marsrs-react-native-xlog 0.1.0-alpha.3 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,18 +16,23 @@ const xlog = Xlog.open({
16
16
  });
17
17
  xlog.i('Net', 'hello');
18
18
  xlog.log(LogLevel.Debug, 'Net', 'a debug line');
19
- xlog.flush(true);
19
+ xlog.flushNow();
20
20
  xlog.close();
21
21
  ```
22
22
 
23
23
  `Xlog` is one appender: `Xlog.open(config)` opens it and `close()` closes it —
24
24
  the same class, under the same name, the Swift package,
25
- `platforms/kmp/marsrs-xlog` and the Android AAR publish. Nothing here answers
26
- a `Promise` and nothing needs an `await`: the module is a TurboModule, so a
27
- call is made on the JS thread and returned from, and the settings are the
25
+ `platforms/kmp/marsrs-xlog` and the Android AAR publish. The settings are the
28
26
  properties they are in Kotlin and in Swift — `xlog.level = LogLevel.Debug` —
29
27
  and not `setLevel` / `getLevel` pairs.
30
28
 
29
+ The drain is three calls and not one with a flag: `xlog.requestFlush()` tells
30
+ the writer thread it may take the cache to the file and returns at once,
31
+ `xlog.flushNow()` does the same on the calling thread and is over when it
32
+ returns, and `await xlog.flush()` is `flushNow` off the JS thread — the one
33
+ call here that answers a `Promise`, because a drain blocks the thread it runs
34
+ on and the JS thread is not one to block.
35
+
31
36
  That is what the New Architecture buys, and it is the only thing asked for in
32
37
  return: a TurboModule is not a bridge module, so an app still on the old
33
38
  architecture has no module to `TurboModuleRegistry.getEnforcing`.
@@ -54,7 +59,7 @@ compiles — there is no build step, and `npm run typecheck` is the only script.
54
59
  `src/NativeXlog.ts` is the spec React Native's codegen reads, and
55
60
  `codegenConfig` in `package.json` is what points it at `src`: `NativeXlogSpec`
56
61
  is generated into the app's `React-Codegen` pod and into the Android build, and
57
- neither half of the module has to be told the eleven signatures twice.
62
+ neither half of the module has to be told the sixteen signatures twice.
58
63
 
59
64
  ## What each platform resolves
60
65
 
@@ -76,9 +81,8 @@ package out of it.
76
81
 
77
82
  ## Why it is called xlog's
78
83
 
79
- The C ABI is 28 `mars_xlog_*` symbols and nothing else, and
80
- `scripts/build_xcframework.sh` fails the day it is not — the same reason the
81
- framework and the AAR are called xlog's. `marsrs-react-native` is the umbrella
84
+ The C ABI is 17 `mars_xlog_*` symbols and nothing else, which is the same
85
+ reason the framework and the AAR are called xlog's. `marsrs-react-native` is the umbrella
82
86
  the whole port joins under: STN and SDT land there and not here, so an app that
83
87
  only logs keeps this one and downloads nothing it does not call. Today the two
84
88
  are the same module under two names, and taking the umbrella costs exactly what
@@ -45,5 +45,5 @@ dependencies {
45
45
  // `react-android` and not `react-native`: the artifact React Native has
46
46
  // published since 0.71, and the one the app's own repository resolves.
47
47
  implementation("com.facebook.react:react-android")
48
- implementation("io.github.orangeboychen.marsrs:xlog:0.1.0-alpha.3")
48
+ implementation("io.github.orangeboychen.marsrs:xlog:0.1.0-alpha.4")
49
49
  }
@@ -1,12 +1,12 @@
1
- // The Android half of `marsrs-react-native-xlog`: the eleven methods of the
2
- // `Xlog` native module, each of them a straight call of a member of `Xlog` — the
3
- // Kotlin face of `libmarsrsxlog.so` in the `marsrs-xlog` AAR, and the same
1
+ // The Android half of `marsrs-react-native-xlog`: the sixteen methods of the
2
+ // `Xlog` native module, each of them a straight call of a member of `Xlog` —
3
+ // the Kotlin face of `libmarsrsxlog.so` in the `marsrs-xlog` AAR, and the same
4
4
  // class `platforms/kmp/marsrs-xlog` publishes to a Kotlin Multiplatform app
5
5
  // and `platforms/apple/MarsRSXlog/Xlog.swift` to a Swift one. So what this
6
6
  // file is is a native module over an API that already exists, and nothing of
7
7
  // the API is invented here: `Xlog.open(XlogConfig(...))`, `log`,
8
- // `isLoggable`, `flush`, `close`, and the five settings, under the names
9
- // every other platform of the port gives them.
8
+ // `isLoggable`, `requestFlush`, `flushNow`, `flush`, `close`, and the five
9
+ // settings, under the names every other platform of the port gives them.
10
10
  //
11
11
  // A TurboModule, and not a bridge module: `src/NativeXlog.ts` is the spec
12
12
  // codegen reads, and `NativeXlogSpec` — the abstract class it generates into
@@ -30,14 +30,19 @@
30
30
 
31
31
  package io.github.orangeboychen.marsrs.xlog.reactnative
32
32
 
33
+ import com.facebook.react.bridge.Arguments
34
+ import com.facebook.react.bridge.Promise
33
35
  import com.facebook.react.bridge.ReactApplicationContext
34
36
  import com.facebook.react.bridge.ReadableMap
37
+ import com.facebook.react.bridge.WritableArray
35
38
  import io.github.orangeboychen.marsrs.xlog.AppenderMode
36
39
  import io.github.orangeboychen.marsrs.xlog.CompressMode
37
40
  import io.github.orangeboychen.marsrs.xlog.LogLevel
38
41
  import io.github.orangeboychen.marsrs.xlog.Xlog
39
42
  import io.github.orangeboychen.marsrs.xlog.XlogConfig
40
43
  import java.util.concurrent.ConcurrentHashMap
44
+ import java.util.concurrent.Executors
45
+ import java.util.concurrent.RejectedExecutionException
41
46
 
42
47
  /** The Android half of `Xlog`. */
43
48
  class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactContext) {
@@ -49,15 +54,22 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
49
54
  */
50
55
  private val appenders = ConcurrentHashMap<String, Xlog>()
51
56
 
57
+ /**
58
+ * The one thread `flush` drains on: a drain blocks the thread it runs on,
59
+ * and the JS thread is not one to block.
60
+ */
61
+ private val flushQueue = Executors.newSingleThreadExecutor()
62
+
52
63
  /**
53
64
  * `Xlog.open(XlogConfig(...))`: opens the appender of the configuration
54
65
  * `src/index.ts` sent, and answers whether it took it.
55
66
  *
56
- * One appender per prefix: a prefix [appenders] already holds is answered
57
- * as `true` without a second `Xlog` over the first, which would be a handle
58
- * nothing releases. `src/index.ts` does the same, so the two names an app
59
- * holds for one prefix are one appender and `close` on either closes it for
60
- * both.
67
+ * One appender per prefix: `Xlog.open` answers the appender the prefix
68
+ * already has rather than a second one over its files, so asking again is
69
+ * how a prefix another part of the app closed is opened again — and a
70
+ * prefix whose appender is still open is answered as it is. `src/index.ts`
71
+ * does the same, so the two names an app holds for one prefix are one
72
+ * appender and `close` on either closes it for both.
61
73
  *
62
74
  * [Xlog.open] is what refuses a configuration the appender cannot honour —
63
75
  * a blank `logDir` or `namePrefix`, a compression level out of range — and
@@ -67,26 +79,36 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
67
79
  */
68
80
  override fun open(config: ReadableMap): Boolean {
69
81
  val namePrefix = config.string("namePrefix").ifBlank { DEFAULT_NAME_PREFIX }
70
- if (appenders.containsKey(namePrefix)) {
71
- return true
72
- }
73
- val xlogConfig = XlogConfig(
74
- logDir = config.string("logDir"),
75
- namePrefix = namePrefix,
76
- level = LogLevel.of(config.int("level", LogLevel.INFO.ordinal)),
77
- mode = appenderModeOf(config.int("mode", AppenderMode.ASYNC.ordinal)),
78
- pubKey = config.string("pubKey"),
79
- compressMode = compressModeOf(config.int("compressMode", CompressMode.ZLIB.ordinal)),
80
- compressLevel = config.int("compressLevel", DEFAULT_COMPRESS_LEVEL),
81
- cacheDir = config.optionalString("cacheDir"),
82
- cacheDays = config.int("cacheDays", NO_CACHE_DAYS)
83
- )
82
+ // [appenders] holding one is not the same as there being one: the
83
+ // appender of a prefix is one for the whole process, so an `Xlog` of
84
+ // this prefix the app closed itself took it away, and answering `true`
85
+ // for it would leave every later call writing through a handle that
86
+ // has no appender behind it.
87
+ appenders[namePrefix]?.takeIf { it.isOpen }?.let { return true }
88
+ // Both calls and not the open only: `XlogConfig` is where a blank
89
+ // `logDir`, a negative `cacheDays` and a compression level out of
90
+ // range are refused, and it throws before `Xlog.open` is ever reached
91
+ // — so a `try` around the open alone is a `try` around nothing.
84
92
  val xlog = try {
85
- Xlog.open(xlogConfig)
93
+ Xlog.open(
94
+ XlogConfig(
95
+ logDir = config.string("logDir"),
96
+ namePrefix = namePrefix,
97
+ level = LogLevel.of(config.int("level", LogLevel.INFO.ordinal)),
98
+ mode = appenderModeOf(config.int("mode", AppenderMode.ASYNC.ordinal)),
99
+ pubKey = config.string("pubKey"),
100
+ compressMode = compressModeOf(
101
+ config.int("compressMode", CompressMode.ZLIB.ordinal)
102
+ ),
103
+ compressLevel = config.int("compressLevel", DEFAULT_COMPRESS_LEVEL),
104
+ cacheDir = config.optionalString("cacheDir"),
105
+ cacheDays = config.int("cacheDays", NO_CACHE_DAYS)
106
+ )
107
+ )
86
108
  } catch (e: IllegalArgumentException) {
87
109
  return false
88
110
  }
89
- appenders[xlogConfig.namePrefix] = xlog
111
+ appenders[xlog.namePrefix] = xlog
90
112
  return true
91
113
  }
92
114
 
@@ -96,30 +118,100 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
96
118
  * too.
97
119
  */
98
120
  override fun log(namePrefix: String, level: Double, tag: String, message: String) {
99
- appender(namePrefix)?.log(LogLevel.of(level.toInt()), tag, message)
121
+ val native = int32(level) ?: return
122
+ appender(namePrefix)?.log(LogLevel.of(native), tag, message)
123
+ }
124
+
125
+ /** `Xlog.currentLogPath`: the directory this appender writes its files
126
+ * into, or `null` once it is closed. */
127
+ override fun currentLogPath(namePrefix: String): String? = appender(namePrefix)?.currentLogPath
128
+
129
+ /** `Xlog.logFiles`: the day's files that are there. */
130
+ override fun logFiles(namePrefix: String, daysAgo: Double): WritableArray? {
131
+ val day = int32(daysAgo) ?: return Arguments.createArray()
132
+ return appender(namePrefix)?.logFiles(day.toLong())?.let { Arguments.fromList(it) }
133
+ ?: Arguments.createArray()
134
+ }
135
+
136
+ /** `Xlog.logFileNames`: the day's names, whether or not they are there yet. */
137
+ override fun logFileNames(namePrefix: String, daysAgo: Double): WritableArray? {
138
+ val day = int32(daysAgo) ?: return Arguments.createArray()
139
+ return appender(namePrefix)?.logFileNames(day.toLong())?.let { Arguments.fromList(it) }
140
+ ?: Arguments.createArray()
100
141
  }
101
142
 
102
143
  /** `Xlog.isLoggable`: whether a record of the level would be written. */
103
- override fun isLoggable(namePrefix: String, level: Double): Boolean =
104
- appender(namePrefix)?.isLoggable(LogLevel.of(level.toInt())) ?: false
144
+ override fun isLoggable(namePrefix: String, level: Double): Boolean {
145
+ val native = int32(level) ?: return false
146
+ return appender(namePrefix)?.isLoggable(LogLevel.of(native)) ?: false
147
+ }
105
148
 
106
- /** `Xlog.level`, read: what `marsrs-jni` answers, and not what JS holds. */
149
+ /**
150
+ * `Xlog.level`, read: what `marsrs-jni` answers, and not what JS holds.
151
+ *
152
+ * `NONE` and not a level the enum does not carry, which is what the iOS
153
+ * half of this module answers for the same numbers and the answer that is
154
+ * safe: `VERBOSE` is the ordinal `0`, so a `-1` read as a level is the
155
+ * level that logs everything — the opposite of what an appender that
156
+ * writes nothing was asked. An appender there is none of is `NONE` on
157
+ * both halves.
158
+ */
107
159
  override fun getLevel(namePrefix: String): Double =
108
160
  appender(namePrefix)?.level?.ordinal?.toDouble() ?: LogLevel.NONE.ordinal.toDouble()
109
161
 
110
- /** `Xlog.flush`. */
111
- override fun flush(namePrefix: String, sync: Boolean) {
112
- appender(namePrefix)?.flush(sync)
162
+ /** `Xlog.requestFlush`: asks the writer thread to drain, answers nothing. */
163
+ override fun requestFlush(namePrefix: String) {
164
+ appender(namePrefix)?.requestFlush()
165
+ }
166
+
167
+ /** `Xlog.flushNow`: the drain that is over, records on disk, when it returns. */
168
+ override fun flushNow(namePrefix: String) {
169
+ appender(namePrefix)?.flushNow()
170
+ }
171
+
172
+ /**
173
+ * `Xlog.flushNow` off the thread JS runs on: the same drain, on a thread of
174
+ * this module's own, and `promise` settled when it is over.
175
+ *
176
+ * A TurboModule method that answers a promise is the one codegen calls off
177
+ * the JS thread, which is the whole reason this one answers one: a drain
178
+ * blocks the thread it runs on, and the JS thread is not one to block.
179
+ *
180
+ * A flush asked of a module [invalidate] has already shut the queue down
181
+ * is no drain and a settled promise: the `await` in JS does not return
182
+ * from a promise nobody resolved, and there is no appender left to drain.
183
+ */
184
+ override fun flush(namePrefix: String, promise: Promise) {
185
+ try {
186
+ flushQueue.execute {
187
+ // Settled whatever the drain does, and not only when it ends
188
+ // well: a throw inside a `Runnable` is swallowed by the
189
+ // executor that was handed it, and a promise nobody settles
190
+ // is an `await` in JS that never comes back.
191
+ try {
192
+ appender(namePrefix)?.flushNow()
193
+ promise.resolve(null)
194
+ } catch (e: Exception) {
195
+ promise.reject(FLUSH_FAILED, e.message)
196
+ }
197
+ }
198
+ } catch (e: RejectedExecutionException) {
199
+ // The queue is shut down, so the drain it was handed is one it
200
+ // will never run: settle here, and not in it.
201
+ promise.resolve(null)
202
+ }
113
203
  }
114
204
 
115
205
  /** `Xlog.level`. */
116
206
  override fun setLevel(namePrefix: String, level: Double) {
117
- appender(namePrefix)?.level = LogLevel.of(level.toInt())
207
+ val native = int32(level) ?: return
208
+ appender(namePrefix)?.level = LogLevel.of(native)
118
209
  }
119
210
 
120
211
  /** `Xlog.mode`. */
121
212
  override fun setMode(namePrefix: String, mode: Double) {
122
- appender(namePrefix)?.mode = appenderModeOf(mode.toInt())
213
+ val native = int32(mode) ?: return
214
+ appender(namePrefix)?.mode = appenderModeOf(native)
123
215
  }
124
216
 
125
217
  /** `Xlog.consoleLogEnabled`. */
@@ -130,25 +222,96 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
130
222
  /** `Xlog.maxFileSizeBytes`. A `Double` and not a `Long`: the module carries
131
223
  * every JS number as one, and a file size is below 2^53. */
132
224
  override fun setMaxFileSize(namePrefix: String, bytes: Double) {
133
- appender(namePrefix)?.maxFileSizeBytes = bytes.toLong()
225
+ val native = int64(bytes) ?: return
226
+ appender(namePrefix)?.maxFileSizeBytes = native
134
227
  }
135
228
 
136
229
  /** `Xlog.maxAliveTimeSeconds`. */
137
230
  override fun setMaxAliveTime(namePrefix: String, seconds: Double) {
138
- appender(namePrefix)?.maxAliveTimeSeconds = seconds.toLong()
231
+ val native = int64(seconds) ?: return
232
+ appender(namePrefix)?.maxAliveTimeSeconds = native
139
233
  }
140
234
 
141
- /** `Xlog.close`: releases the appender `open` made. */
235
+ /**
236
+ * `Xlog.close`: releases the appender `open` made.
237
+ *
238
+ * A method that answers no promise is a call codegen makes on the JS
239
+ * thread, and `Xlog.close` is a drain of everything the appender is still
240
+ * holding and a write of the banner that ends the file — so this one is
241
+ * handed to the queue, the way [flush] is, and the JS thread is free again
242
+ * at once.
243
+ *
244
+ * Taken out of [appenders] here and not on the queue: a reopen of the
245
+ * prefix that lands while the drain is queued is otherwise an appender
246
+ * this close never held. What a queue that was shut down already means is
247
+ * that [closeAll] ran, and with it this close.
248
+ */
142
249
  override fun close(namePrefix: String) {
143
- appenders.remove(namePrefix)?.close()
250
+ val appender = appenders.remove(namePrefix) ?: return
251
+ try {
252
+ flushQueue.execute { appender.close() }
253
+ } catch (e: RejectedExecutionException) {
254
+ appender.close()
255
+ }
256
+ }
257
+
258
+ /**
259
+ * What React Native calls when the bridge this module was made for goes
260
+ * away: the one place every appender [open] made is closed — see
261
+ * [closeAll] — and the one place the thread [flushQueue] runs on is shut
262
+ * down:
263
+ * `Executors.newSingleThreadExecutor` keeps a non-daemon thread alive
264
+ * until something shuts it, so a module that did not would leave one
265
+ * behind per bridge reload — each of them idle, and each of them a
266
+ * process that will not end while they are there.
267
+ */
268
+ override fun invalidate() {
269
+ super.invalidate()
270
+ closeAll()
271
+ flushQueue.shutdown()
272
+ }
273
+
274
+ /**
275
+ * Every appender [open] made, closed: what a module that is going away
276
+ * owes the files it was writing, and the only drain they will get — the
277
+ * queue is shut down below, so an appender left open is one whose records
278
+ * stay in its buffer, whose writer thread stays alive and whose cache
279
+ * stays claimed for the life of the process.
280
+ *
281
+ * Closed on the queue and not on this thread, which is the JS thread,
282
+ * because `Xlog.close` is itself a drain; and `shutdown` runs what it was
283
+ * handed before it stops, so these do run. What is not left to the queue
284
+ * is the map: [appenders] is emptied before the drain is handed over, so a
285
+ * `close` that arrives in the window between the two has no appender of
286
+ * its own to drain on the JS thread. A queue that refuses the drain — an
287
+ * `invalidate` before this one — is one this closes here instead.
288
+ */
289
+ private fun closeAll() {
290
+ // Taken out of [appenders] here, and not on the queue below: a `close`
291
+ // of a prefix that lands before the drain runs is then a `close` with
292
+ // no appender to find, where the old order left it in the map for the
293
+ // length of the drain — and the window between this call and
294
+ // `flushQueue.shutdown()` is exactly when such a `close` arrives,
295
+ // because `shutdown` refuses what it is handed next and not what is
296
+ // already queued.
297
+ val closing = appenders.values.toList()
298
+ appenders.clear()
299
+ try {
300
+ flushQueue.execute { closing.forEach { it.close() } }
301
+ } catch (e: RejectedExecutionException) {
302
+ // A queue that refuses the drain is one this closes here: an
303
+ // appender that is going away with the module is drained now or
304
+ // not at all, and the records it still holds go with it.
305
+ closing.forEach { it.close() }
306
+ }
144
307
  }
145
308
 
146
309
  /**
147
310
  * The appender of [namePrefix], or `null` when there is none — which is a
148
311
  * no-op and not a crash, the same answer the Swift and the Kotlin give an
149
- * `Xlog` that is closed: no appender is the process-wide one to
150
- * `marsrs-jni`, so a call that went on without one would write through
151
- * whatever appender the rest of the process writes through.
312
+ * `Xlog` that is closed: a handle whose appender is gone is a no-op to
313
+ * `marsrs-jni`, so a call that went on without one would silently write
314
+ * nothing.
152
315
  */
153
316
  private fun appender(namePrefix: String): Xlog? = appenders[namePrefix]?.takeIf { it.isOpen }
154
317
 
@@ -164,9 +327,12 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
164
327
  /** `0` — what the C++ passes on, which is the appender's own 6. */
165
328
  private const val DEFAULT_COMPRESS_LEVEL = 0
166
329
 
167
- /** `0` keeps every log file, which is what the C++'s default is. */
330
+ /** `0` keeps every cache file, which is what the C++'s default is. */
168
331
  private const val NO_CACHE_DAYS = 0
169
332
 
333
+ /** What a drain that threw is answered with: see [flush]. */
334
+ private const val FLUSH_FAILED = "E_XLOG_FLUSH"
335
+
170
336
  /**
171
337
  * `AppenderMode` of the number `mars_xlog.h` gives a mode: an ordinal
172
338
  * the enum does not carry is one the caller sent by mistake, and `ASYNC`
@@ -182,6 +348,45 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
182
348
  CompressMode.ZSTD.ordinal -> CompressMode.ZSTD
183
349
  else -> CompressMode.ZLIB
184
350
  }
351
+
352
+ /**
353
+ * The number JS sent for a level, a mode or a day, and `null` when it
354
+ * is not one: a `NaN`, an infinity, and anything the `Int` the C ABI
355
+ * takes cannot hold.
356
+ *
357
+ * `Double.toInt()` answers `0` for a `NaN`, and `LogLevel.of` answers
358
+ * `VERBOSE` for a `0`, so a level JS computed and came to no level
359
+ * with — a sum with an `undefined` in it — was one the appender was
360
+ * *moved* to, and the one it was moved to is the level that logs
361
+ * everything: an app that meant to log nothing logged all of it. The
362
+ * iOS half of this module read the same number with a trap rather
363
+ * than a `0` — `Int32(_:)` on a `NaN` ends the app — and neither is
364
+ * an answer, so both leave the appender alone now.
365
+ */
366
+ private fun int32(value: Double): Int? {
367
+ if (!value.isFinite() || value < Int.MIN_VALUE.toDouble() || value > Int.MAX_VALUE.toDouble()) {
368
+ return null
369
+ }
370
+ return value.toInt()
371
+ }
372
+
373
+ /**
374
+ * The number JS sent for a size or a file lifetime, and `null` when it
375
+ * is not one: the same three, over the `Long` the Kotlin takes. A
376
+ * `NaN` was answered as `0` — "never split" for a size, and for a
377
+ * lifetime the one an appender opened with none keeps, the C++'s own
378
+ * ten days, which no setter can ask for either — and that is a setting
379
+ * an app never asked for and not one it was told had been refused.
380
+ */
381
+ private fun int64(value: Double): Long? {
382
+ if (!value.isFinite() ||
383
+ value < Long.MIN_VALUE.toDouble() ||
384
+ value >= Long.MAX_VALUE.toDouble()
385
+ ) {
386
+ return null
387
+ }
388
+ return value.toLong()
389
+ }
185
390
  }
186
391
  }
187
392
 
@@ -10,15 +10,16 @@
10
10
  <key>HeadersPath</key>
11
11
  <string>Headers</string>
12
12
  <key>LibraryIdentifier</key>
13
- <string>watchos-arm64-simulator</string>
13
+ <string>ios-arm64_x86_64-simulator</string>
14
14
  <key>LibraryPath</key>
15
15
  <string>libmars_ffi.a</string>
16
16
  <key>SupportedArchitectures</key>
17
17
  <array>
18
18
  <string>arm64</string>
19
+ <string>x86_64</string>
19
20
  </array>
20
21
  <key>SupportedPlatform</key>
21
- <string>watchos</string>
22
+ <string>ios</string>
22
23
  <key>SupportedPlatformVariant</key>
23
24
  <string>simulator</string>
24
25
  </dict>
@@ -45,7 +46,7 @@
45
46
  <key>HeadersPath</key>
46
47
  <string>Headers</string>
47
48
  <key>LibraryIdentifier</key>
48
- <string>ios-arm64</string>
49
+ <string>watchos-arm64-simulator</string>
49
50
  <key>LibraryPath</key>
50
51
  <string>libmars_ffi.a</string>
51
52
  <key>SupportedArchitectures</key>
@@ -53,7 +54,9 @@
53
54
  <string>arm64</string>
54
55
  </array>
55
56
  <key>SupportedPlatform</key>
56
- <string>ios</string>
57
+ <string>watchos</string>
58
+ <key>SupportedPlatformVariant</key>
59
+ <string>simulator</string>
57
60
  </dict>
58
61
  <dict>
59
62
  <key>BinaryPath</key>
@@ -61,18 +64,15 @@
61
64
  <key>HeadersPath</key>
62
65
  <string>Headers</string>
63
66
  <key>LibraryIdentifier</key>
64
- <string>ios-arm64_x86_64-simulator</string>
67
+ <string>ios-arm64</string>
65
68
  <key>LibraryPath</key>
66
69
  <string>libmars_ffi.a</string>
67
70
  <key>SupportedArchitectures</key>
68
71
  <array>
69
72
  <string>arm64</string>
70
- <string>x86_64</string>
71
73
  </array>
72
74
  <key>SupportedPlatform</key>
73
75
  <string>ios</string>
74
- <key>SupportedPlatformVariant</key>
75
- <string>simulator</string>
76
76
  </dict>
77
77
  </array>
78
78
  <key>CFBundlePackageType</key>