|
1 | | -## android typings |
| 1 | +# Contributing |
2 | 2 |
|
| 3 | +## What is generated and what is not |
| 4 | + |
| 5 | +| Path | Owner | |
| 6 | +| --- | --- | |
| 7 | +| `src/ui-massifmaps/typings/*.d.ts` | generated — `npm run typings` | |
| 8 | +| `src/ui-massifmaps/bindings/**` | generated — `npm run bindings` | |
| 9 | +| `packages/ui-massifmaps/platforms/{android,ios}/native-api-usage.json` | generated — `npm run bindings` | |
| 10 | +| everything else under `src/ui-massifmaps/` | hand-written wrappers | |
| 11 | +| `packages/ui-massifmaps/**` (except the two files above) | build output — `npm run build` | |
| 12 | + |
| 13 | +Generated files carry a `GENERATED FILE - do not edit by hand` header. Editing one is always the |
| 14 | +wrong fix: change the generator, or change the hand-written wrapper it feeds. |
| 15 | + |
| 16 | +Both scripts take `-h`: |
| 17 | + |
| 18 | +```sh |
| 19 | +node scripts/typings/index.mjs -h |
| 20 | +npm run bindings -- -h |
3 | 21 | ``` |
4 | | -java -jar build/libs/dts-generator.jar -skip-declarations -input /Volumes/data/dev/massif-maps/MassifMaps/dist/android/massif.jar |
| 22 | + |
| 23 | +## Updating to a new SDK release |
| 24 | + |
| 25 | +1. **Point the plugin at the new SDK.** |
| 26 | + - android: bump the `massifSDKVersion` default in |
| 27 | + `packages/ui-massifmaps/platforms/android/include.gradle`. |
| 28 | + - iOS: replace `packages/ui-massifmaps/platforms/ios/MassifMaps.xcframework/`. |
| 29 | + |
| 30 | +2. **Regenerate the ambient typings.** |
| 31 | + |
| 32 | + ```sh |
| 33 | + npm run typings |
| 34 | + ``` |
| 35 | + |
| 36 | + Android reads the maven coordinates and default version straight out of `include.gradle`, so |
| 37 | + the typings can never be generated against a version the plugin does not link. It resolves the |
| 38 | + `.aar` from the gradle cache or downloads it, then runs `dts-generator.jar` — running |
| 39 | + `ns prepare android` in `demo-svelte` first if the jar is not there yet. |
| 40 | + |
| 41 | + iOS is a full `ns typings ios` build in `demo-svelte` and takes minutes cold. `--no-build` |
| 42 | + reuses whatever is already in `demo-svelte/typings/ios`. |
| 43 | + |
| 44 | + `ns.massifmaps.android.d.ts` (the plugin's own java additions) needs |
| 45 | + `packages/ui-massifmaps/platforms/android/ui_massifmaps.aar`, which only exists after a demo |
| 46 | + android build. Without it the script says so and skips that file; `--skip-additions` asks for |
| 47 | + the same thing explicitly. |
| 48 | + |
| 49 | +3. **Regenerate the binding tables and the native-api-usage files.** |
| 50 | + |
| 51 | + ```sh |
| 52 | + npm run bindings |
| 53 | + ``` |
| 54 | + |
| 55 | +4. **See what the new SDK brought in.** |
| 56 | + |
| 57 | + ```sh |
| 58 | + npm run bindings -- --coverage # unforwarded methods, and per-platform gaps |
| 59 | + npm run bindings -- --audit # hand-written classes vs the model |
| 60 | + ``` |
| 61 | + |
| 62 | + `--coverage` reports four kinds of gap besides the unforwarded methods, all of which are |
| 63 | + silent at build time: |
| 64 | + |
| 65 | + - a binding table bound in `index.android.ts` but not `index.ios.ts`, or the reverse — |
| 66 | + the property then does nothing on the other platform instead of failing; |
| 67 | + - a wrapper class implemented on one platform only; |
| 68 | + - a class both platforms implement that no `.d.ts` declares, so nothing importing the |
| 69 | + plugin can see it; |
| 70 | + - an SDK class with a generated table and no wrapper at all. |
| 71 | + |
| 72 | + It exits non-zero on any of those, except a class whose SDK counterpart is android-only |
| 73 | + to begin with — that one is reported and marked, not counted. |
| 74 | + |
| 75 | +5. **Typecheck, then run both demos.** |
| 76 | + |
| 77 | + ```sh |
| 78 | + ./node_modules/.bin/tsc --build packages/ui-massifmaps/tsconfig.json |
| 79 | + npm run demo.svelte.android |
| 80 | + npm run demo.svelte.ios |
| 81 | + ``` |
| 82 | + |
| 83 | +## When the SDK adds a class |
| 84 | + |
| 85 | +`--coverage` lists every SDK class with no binding table. For each one worth exposing: |
| 86 | + |
| 87 | +```sh |
| 88 | +npm run bindings -- --scaffold RasterTileFilter # or --scaffold-all |
| 89 | +npm run bindings -- --scaffold CelestialArc, CelestialLayer, CelestialSprite # several at once |
5 | 90 | ``` |
6 | 91 |
|
7 | | -### clean up typings |
8 | | -* regexp:```declare module com \{\n\t*export module massifmaps \{\n\t*export module \w* \{\n\t*export class \w*JNI extends [\w.]* \{([^}]|\n)*\}\n\t*\}\n\t*\}\n\}``` |
9 | | -* regexp:```declare module com \{\n\t*export module massifmaps \{\n\t*export module \w* \{\n\t*export module \w* \{\n\t*export class \w*JNI extends [\w.]* \{([^}]|\n)*\}\n\t*\}\n\t*\}\n\t*\}\n\}``` |
10 | | -* regexp:```\t*public (delete|swigGetDirectorObject|swigGetClassName|swigGetClassName|swigDirectorDisconnect|swigReleaseOwnership|finalize|swigTakeOwnership)\(\): (void|number|string|any);\n``` |
11 | | -* regexp:```\t*public static (swigCreatePolymorphicInstance|getCPtr)\(.*\): .*;\n``` |
12 | | -* regexp:```\t*public (swigCMemOwn): .*;\n``` |
| 92 | +Scaffolds land in `scaffold/` (never `src/`) as three files per class — `<Name>.d.ts`, |
| 93 | +`<Name>.android.ts` and `<Name>.ios.ts`, the same three legs every module in `src/` has. The |
| 94 | +`.d.ts` carries the `<Name>Options` interface and the `Accessors`/`Methods` merge, so a scaffolded |
| 95 | +class is not missing its public types. |
13 | 96 |
|
14 | | -<!-- * regexp: ```/export class .*?JNI {(.|[\r\n])*?}//``` |
15 | | -* regexp: ```/export module .*? {([\t\r\n])*?}//``` twice |
16 | | -* regexp: ```/declare module com {([\t\r\n])*?}/``` --> |
| 97 | +A class taking a listener — `setCelestialEventListener(CelestialEventListener)` — also gets the |
| 98 | +glue, which is the same shape every time: a plain TS interface in the `.d.ts`, the |
| 99 | +`com.nativescript.massifmaps.additions.*` subclass wired up on android, an `NSMSF*` delegate class |
| 100 | +on iOS, one stub per callback, and an `exclude` on `bindNative` so the synthesised accessor cannot |
| 101 | +bypass any of it. Only the argument marshalling is left as a TODO. |
17 | 102 |
|
18 | | -## ios typings |
| 103 | +The native halves come with it when the plugin does not already ship them: |
19 | 104 |
|
20 | | -run in the demo app |
21 | 105 | ``` |
22 | | -TNS_TYPESCRIPT_DECLARATIONS_PATH="$(pwd)/typings" tns build ios --bundle |
| 106 | +scaffold/platforms/android/java/com/nativescript/massifmaps/additions/<X>.java |
| 107 | +scaffold/platforms/ios/src/NSMSF<X>.h |
| 108 | +scaffold/platforms/ios/src/NSMSF<X>.mm |
23 | 109 | ``` |
24 | 110 |
|
25 | | -### clean up typings |
| 111 | +Both are the same main-thread hop every existing listener does — `SynchronousHandler.postAndWait` |
| 112 | +on android, `dispatch_sync(dispatch_get_main_queue(), ...)` on iOS — around a `<name>Threaded` |
| 113 | +stub the TypeScript delegate overrides. The ObjC selectors are read out of the SDK's own framework |
| 114 | +header, labels included, because an override that misses a label compiles and then silently never |
| 115 | +fires. Copy the files into `packages/ui-massifmaps/platforms/`, then add the `#import` line the |
| 116 | +command prints to `platforms/ios/src/MassifMapsAdditions.h` — the clang module does not see a |
| 117 | +header the umbrella does not name. |
| 118 | + |
| 119 | +Pick the right native constructor, fill in the TODOs, move all three into |
| 120 | +`src/ui-massifmaps/<pkg>/`, and export them from that package's index. Then re-run |
| 121 | +`npm run bindings` so the class reaches `native-api-usage.json`, and |
| 122 | +`npm run bindings -- --coverage` to confirm nothing is left on one platform only. |
| 123 | + |
| 124 | +A class android has and iOS does not is reported at the end of `npm run bindings` and is |
| 125 | +deliberately not bound — the binding tables only cover what both platforms have. |
| 126 | + |
| 127 | +## When the SDK changes an existing API |
| 128 | + |
| 129 | +Regenerate the typings first, then: |
26 | 130 |
|
27 | | -* regexp: ```/description\(\): string;//``` |
28 | | -* regexp: ```/hash\(\): number;//``` |
29 | | -* regexp: ```/var:/variant:/``` |
| 131 | +```sh |
| 132 | +npm run bindings -- --audit |
| 133 | +``` |
| 134 | + |
| 135 | +It reports three things: |
| 136 | + |
| 137 | +- **properties declared with no matching native accessor** — the SDK renamed or dropped the |
| 138 | + accessor behind a `@nativeProperty`. A wrapper targeting one of our own `NSMSF*`/`additions` |
| 139 | + subclasses shows up here legitimately: the SDK base class genuinely does not declare those |
| 140 | + members. |
| 141 | +- **properties whose decorator disagrees with the native type** — e.g. a plain `@nativeProperty` |
| 142 | + where the accessor now returns a `Color` and needs `colorConverter`. |
| 143 | +- **classes with native accessors the plugin does not expose** — new API on a class already wrapped. |
| 144 | + |
| 145 | +To move a hand-written class onto the generated table: |
| 146 | + |
| 147 | +```sh |
| 148 | +npm run bindings -- --migrate RasterTileLayer VectorTileLayer # or --migrate-all |
| 149 | +``` |
| 150 | + |
| 151 | +That attaches `METHODS`/`ACCESSORS`/`SELECTORS` and prunes the `@nativeProperty` declarations the |
| 152 | +table now covers (`--no-prune` keeps them). Review the diff: a class that marshals some members |
| 153 | +itself needs an `exclude` for them. |
| 154 | + |
| 155 | +## Enums |
| 156 | + |
| 157 | +Enums are not bound. They are hand-written constant objects that read the native value: |
| 158 | + |
| 159 | +```ts |
| 160 | +export const RasterTileFilterMode = { |
| 161 | + get RASTER_TILE_FILTER_MODE_NEAREST() { |
| 162 | + return com.massifmaps.layers.RasterTileFilterMode.RASTER_TILE_FILTER_MODE_NEAREST; |
| 163 | + }, |
| 164 | + ... |
| 165 | +}; |
| 166 | +``` |
| 167 | + |
| 168 | +That works because android compiles a SWIG enum to a real java class with static fields. iOS |
| 169 | +compiles the same enum to a `const enum` — plain integers, nothing to look up. |
| 170 | + |
| 171 | +The android consequence: **every SWIG enum needs a `native-api-usage.json` entry**, or the metadata |
| 172 | +filter strips the class and the getter returns `undefined`. `npm run bindings` emits all of them — |
| 173 | +do not maintain the list by hand. Whitelisting only the enums a bound method mentions is not |
| 174 | +enough: `MBTilesScheme` appears solely in a constructor signature, which the parser drops as noise. |
| 175 | + |
| 176 | +## native-api-usage.json |
| 177 | + |
| 178 | +Both files come out of the same model, so they cannot drift apart: |
| 179 | + |
| 180 | +- android matches `package:Class` — `com.massifmaps.layers:RasterTileLayer` |
| 181 | +- iOS matches `clangModule:ObjCInterface` — `MassifMaps:MSFRasterTileLayer` |
| 182 | + |
| 183 | +`npm run bindings -- --no-api-usage` skips them. |
| 184 | + |
| 185 | +A consuming app has its own `App_Resources/Android/native-api-usage.json` with |
| 186 | +`"whitelist-plugins-usages": true`, which is what pulls this plugin's `uses` list in. An app only |
| 187 | +needs its own entries for SDK classes it touches directly, outside the plugin's wrappers. |
| 188 | + |
| 189 | +## Fallback: generating typings by hand |
| 190 | + |
| 191 | +Only if `scripts/typings` fails. The cleanup passes it does — stripping `*JNI` classes, swig |
| 192 | +plumbing (`delete`, `swigGetDirectorObject`, `swigCreatePolymorphicInstance`, `swigCMemOwn`, …), |
| 193 | +ObjC `description()`/`hash()`, and reserved parameter names — then have to be redone by hand. See |
| 194 | +`scripts/typings/swig.mjs` for what they are. |
| 195 | + |
| 196 | +android: |
| 197 | + |
| 198 | +```sh |
| 199 | +java -jar build/libs/dts-generator.jar -skip-declarations -input path/to/massif.jar |
| 200 | +``` |
| 201 | + |
| 202 | +iOS, from `demo-svelte`: |
| 203 | + |
| 204 | +```sh |
| 205 | +TNS_TYPESCRIPT_DECLARATIONS_PATH="$(pwd)/typings" ns build ios --bundle |
| 206 | +``` |
0 commit comments