zano-native 0.0.1
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/CHANGELOG.md +90 -0
- package/LICENSE +27 -0
- package/README.md +24 -0
- package/android/build.gradle +40 -0
- package/android/src/main/java/app/edge/rnzano/RnZanoModule.java +73 -0
- package/android/src/main/java/app/edge/rnzano/RnZanoPackage.java +21 -0
- package/android/src/main/jniLibs/arm64-v8a/librnzano.so +0 -0
- package/ios/ZanoModule.h +4 -0
- package/ios/ZanoModule.mm +138 -0
- package/ios/ZanoModule.xcframework/Info.plist +43 -0
- package/ios/ZanoModule.xcframework/ios-arm64/libzano-module.a +0 -0
- package/ios/ZanoModule.xcframework/ios-arm64-simulator/libzano-module.a +0 -0
- package/ios/react-native-zano.xcodeproj/project.pbxproj +1 -0
- package/lib/scripts/build-native-host.d.ts +1 -0
- package/lib/scripts/build-native-host.js +175 -0
- package/lib/scripts/smoke-node.d.ts +1 -0
- package/lib/scripts/smoke-node.js +33 -0
- package/lib/scripts/update-sources.d.ts +1 -0
- package/lib/scripts/update-sources.js +412 -0
- package/lib/scripts/utils/android-tools.d.ts +1 -0
- package/lib/scripts/utils/android-tools.js +25 -0
- package/lib/scripts/utils/closeWalletPatch.d.ts +23 -0
- package/lib/scripts/utils/closeWalletPatch.js +215 -0
- package/lib/scripts/utils/common.d.ts +37 -0
- package/lib/scripts/utils/common.js +186 -0
- package/lib/scripts/utils/ios-tools.d.ts +8 -0
- package/lib/scripts/utils/ios-tools.js +26 -0
- package/lib/scripts/utils/sdkFolders.d.ts +27 -0
- package/lib/scripts/utils/sdkFolders.js +43 -0
- package/lib/src/CppBridge.d.ts +142 -0
- package/lib/src/CppBridge.js +668 -0
- package/lib/src/index.d.ts +4 -0
- package/lib/src/index.js +28 -0
- package/lib/src/load-addon.d.ts +5 -0
- package/lib/src/load-addon.js +50 -0
- package/lib/src/node.d.ts +11 -0
- package/lib/src/node.js +25 -0
- package/lib/src/types.d.ts +292 -0
- package/lib/src/types.js +40 -0
- package/lib/src/walletFilePassword.d.ts +14 -0
- package/lib/src/walletFilePassword.js +74 -0
- package/node.d.ts +5 -0
- package/node.js +2 -0
- package/package.json +105 -0
- package/prebuilds/darwin-arm64/zano.node +0 -0
- package/src/node/zano-napi.cpp +176 -0
- package/src/zano-wrapper/zano-methods.hpp +15 -0
- package/zano-native.podspec +27 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.patchCloseWallet = void 0;
|
|
4
|
+
const patchMarker = 'Edge patch: close-during-refresh deadlock';
|
|
5
|
+
const hint = 'The SDK sources changed at this pin. If upstream has fixed the ' +
|
|
6
|
+
'close-during-refresh lock inversion, delete this patch; otherwise port ' +
|
|
7
|
+
'it to the new body.';
|
|
8
|
+
/**
|
|
9
|
+
* `close_wallet` as pinned at zano_native_lib 91085c0, modulo invisible
|
|
10
|
+
* trailing whitespace. The transform refuses to run unless the function it
|
|
11
|
+
* found matches this byte-for-byte after trailing whitespace is stripped,
|
|
12
|
+
* so ANY upstream drift - not just drift through the lines the deadlock
|
|
13
|
+
* analysis rests on - fails the build for a human to re-evaluate.
|
|
14
|
+
*/
|
|
15
|
+
const original = `std::string wallets_manager::close_wallet(size_t wallet_id)
|
|
16
|
+
{
|
|
17
|
+
EXCLUSIVE_CRITICAL_REGION_LOCAL(m_wallets_lock);
|
|
18
|
+
|
|
19
|
+
auto it = m_wallets.find(wallet_id);
|
|
20
|
+
if (it == m_wallets.end())
|
|
21
|
+
return API_RETURN_CODE_WALLET_WRONG_ID;
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
try
|
|
25
|
+
{
|
|
26
|
+
it->second.major_stop = true;
|
|
27
|
+
it->second.stop_for_refresh = true;
|
|
28
|
+
it->second.w.unlocked_get()->stop();
|
|
29
|
+
|
|
30
|
+
it->second.w->get()->store();
|
|
31
|
+
m_wallets.erase(it);
|
|
32
|
+
{
|
|
33
|
+
CRITICAL_REGION_LOCAL(m_wallet_log_prefixes_lock);
|
|
34
|
+
m_wallet_log_prefixes[wallet_id] = std::string("[") + epee::string_tools::num_to_string_fast(wallet_id) + ":CLOSED] ";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
catch (const std::exception& e)
|
|
39
|
+
{
|
|
40
|
+
return std::string(API_RETURN_CODE_FAIL) + ":" + e.what();
|
|
41
|
+
}
|
|
42
|
+
catch (...)
|
|
43
|
+
{
|
|
44
|
+
return API_RETURN_CODE_INTERNAL_ERROR;
|
|
45
|
+
}
|
|
46
|
+
//m_pview->hide_wallet();
|
|
47
|
+
return API_RETURN_CODE_OK;
|
|
48
|
+
}`;
|
|
49
|
+
/**
|
|
50
|
+
* The replacement `close_wallet`. The pinned original holds
|
|
51
|
+
* `m_wallets_lock` exclusively across two waits on the wallet being
|
|
52
|
+
* closed: the `store()` call, which needs the per-wallet lock the refresh
|
|
53
|
+
* worker holds for the whole of a scan chunk, and the map erase, whose
|
|
54
|
+
* destructor joins the worker thread. The refresh path re-enters the
|
|
55
|
+
* manager through wallet callbacks (`on_transfer2`, `on_transfer_canceled`)
|
|
56
|
+
* that take `m_wallets_lock` shared, and every other API call takes it
|
|
57
|
+
* shared up front - so a close issued while the wallet is catching up
|
|
58
|
+
* wedges the worker, the close, and then every Zano call in the process,
|
|
59
|
+
* permanently. Thread dumps of the frozen app show the close parked inside
|
|
60
|
+
* the native call, and no checkpoint ever lands again.
|
|
61
|
+
*
|
|
62
|
+
* The rewrite detaches the map node while holding the lock, then does the
|
|
63
|
+
* slow parts - the store and the implicit thread join when the node dies
|
|
64
|
+
* - with no manager lock held at all. Node extraction keeps the element
|
|
65
|
+
* at its address, so the worker thread's references stay valid; wallet ids
|
|
66
|
+
* are never reused, so late callbacks cannot hit a recycled id. The
|
|
67
|
+
* log-prefix write gains a bounds check, because the manager lock no
|
|
68
|
+
* longer serializes it against `reset()`'s vector clear.
|
|
69
|
+
*
|
|
70
|
+
* Deliberate semantics changes, all confined to the window after the node
|
|
71
|
+
* leaves the map:
|
|
72
|
+
*
|
|
73
|
+
* - "Absent from `m_wallets`" no longer implies "closed and stored".
|
|
74
|
+
* During the store/join window the wallet is invisible to
|
|
75
|
+
* `open_wallet`'s ALREADY_EXISTS check and to status calls while its
|
|
76
|
+
* `wallet2` still writes the file. Safe for this bridge, which strictly
|
|
77
|
+
* sequences close-before-reopen per wallet on one executor thread; a
|
|
78
|
+
* caller without that discipline could double-open the file. Say so in
|
|
79
|
+
* any upstream submission.
|
|
80
|
+
* - The refresh worker's own callbacks miss where they used to hit. Both
|
|
81
|
+
* `on_transfer2` and `on_transfer_canceled` look the wallet up by id
|
|
82
|
+
* under a shared `m_wallets_lock` while the store runs, and the entry is
|
|
83
|
+
* no longer there. Both lookups are checked - `on_transfer2` through
|
|
84
|
+
* `GET_WALLET_OPTIONS_BY_ID_VOID_RET`, which returns on a miss, and
|
|
85
|
+
* `on_transfer_canceled` with an explicit `end()` test that logs and
|
|
86
|
+
* returns - so the cost is a dropped view notification for a wallet
|
|
87
|
+
* that is closing anyway, not an unchecked dereference. Every
|
|
88
|
+
* `m_wallets.find` in the file is checked this way; the three bare
|
|
89
|
+
* `m_wallets[...]` sites all insert a freshly counted id in the open,
|
|
90
|
+
* restore and generate paths, and ids are never reused.
|
|
91
|
+
* - When `store()` throws, the original left the entry in the map as a
|
|
92
|
+
* zombie (its stop flags already set, so it could never sync again);
|
|
93
|
+
* the rewrite reports the same error but the wallet is gone. A
|
|
94
|
+
* `closeWallet` retry therefore reports WALLET_WRONG_ID instead of
|
|
95
|
+
* retrying the store, so `CppBridge`'s re-key migration takes its
|
|
96
|
+
* "leave the file alone" branch with the wallet already released; the
|
|
97
|
+
* next launch retries the migration.
|
|
98
|
+
*
|
|
99
|
+
* iOS links the prebuilt `libzano-plain-wallet` framework and keeps the
|
|
100
|
+
* original blocking semantics throughout.
|
|
101
|
+
*/
|
|
102
|
+
const replacement = `std::string wallets_manager::close_wallet(size_t wallet_id)
|
|
103
|
+
{
|
|
104
|
+
// ${patchMarker}.
|
|
105
|
+
//
|
|
106
|
+
// The original held m_wallets_lock exclusively across store() and across
|
|
107
|
+
// the map erase, whose destructor joins the refresh worker. Both wait on
|
|
108
|
+
// a wallet that may be mid-refresh, and the refresh path re-enters the
|
|
109
|
+
// manager through wallet callbacks that take m_wallets_lock shared - so
|
|
110
|
+
// a close issued during a long refresh held the very lock the worker
|
|
111
|
+
// needed to reach its stop flags, and neither side could ever proceed.
|
|
112
|
+
// Detach the map node under the lock instead, then store and join with
|
|
113
|
+
// no manager lock held. The node is declared outside the try so its
|
|
114
|
+
// destructor - which joins the worker thread the stop flags told to
|
|
115
|
+
// finish - runs at function exit rather than during unwinding, while
|
|
116
|
+
// everything that can throw stays inside the try, so failures keep
|
|
117
|
+
// reporting as return codes exactly as they did before this patch.
|
|
118
|
+
decltype(m_wallets)::node_type wallet_node;
|
|
119
|
+
try
|
|
120
|
+
{
|
|
121
|
+
{
|
|
122
|
+
EXCLUSIVE_CRITICAL_REGION_LOCAL(m_wallets_lock);
|
|
123
|
+
|
|
124
|
+
auto it = m_wallets.find(wallet_id);
|
|
125
|
+
if (it == m_wallets.end())
|
|
126
|
+
return API_RETURN_CODE_WALLET_WRONG_ID;
|
|
127
|
+
|
|
128
|
+
it->second.major_stop = true;
|
|
129
|
+
it->second.stop_for_refresh = true;
|
|
130
|
+
it->second.w.unlocked_get()->stop();
|
|
131
|
+
wallet_node = m_wallets.extract(it);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
wallet_node.mapped().w->get()->store();
|
|
135
|
+
{
|
|
136
|
+
CRITICAL_REGION_LOCAL(m_wallet_log_prefixes_lock);
|
|
137
|
+
// The manager lock no longer serializes this write against reset()'s
|
|
138
|
+
// clear of the vector, so respect its current size:
|
|
139
|
+
if (wallet_id < m_wallet_log_prefixes.size())
|
|
140
|
+
m_wallet_log_prefixes[wallet_id] = std::string("[") + epee::string_tools::num_to_string_fast(wallet_id) + ":CLOSED] ";
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (const std::exception& e)
|
|
144
|
+
{
|
|
145
|
+
return std::string(API_RETURN_CODE_FAIL) + ":" + e.what();
|
|
146
|
+
}
|
|
147
|
+
catch (...)
|
|
148
|
+
{
|
|
149
|
+
return API_RETURN_CODE_INTERNAL_ERROR;
|
|
150
|
+
}
|
|
151
|
+
//m_pview->hide_wallet();
|
|
152
|
+
return API_RETURN_CODE_OK;
|
|
153
|
+
}`;
|
|
154
|
+
/** Strips trailing whitespace per line, the one formatting freedom the
|
|
155
|
+
* comparison allows - the pinned file carries an invisible trailing
|
|
156
|
+
* space that transcriptions of it should not have to reproduce. `\r` is
|
|
157
|
+
* in the class so a CRLF checkout reports the drift it has, rather than
|
|
158
|
+
* one carriage return per line. */
|
|
159
|
+
function normalize(code) {
|
|
160
|
+
return code
|
|
161
|
+
.split('\n')
|
|
162
|
+
.map(line => line.replace(/[ \t\r]+$/, ''))
|
|
163
|
+
.join('\n');
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Rewrites the SDK's `wallets_manager::close_wallet` so it does not hold
|
|
167
|
+
* the wallet-manager lock while waiting on the wallet being closed. See
|
|
168
|
+
* the comment on `replacement` above for the deadlock this removes and
|
|
169
|
+
* the semantics it deliberately changes.
|
|
170
|
+
*
|
|
171
|
+
* Only the Android libraries pick this up: iOS links the prebuilt
|
|
172
|
+
* `libzano-plain-wallet` xcframework rather than building these sources,
|
|
173
|
+
* and iOS runs the same close-during-catch-up cycle without wedging.
|
|
174
|
+
*
|
|
175
|
+
* The function is located by its unique signature, delimited by brace
|
|
176
|
+
* counting, and then required to match the pinned original exactly
|
|
177
|
+
* (modulo trailing whitespace), so a pin bump that changes `close_wallet`
|
|
178
|
+
* in any way fails the build here instead of silently keeping (or
|
|
179
|
+
* dropping) a stale patch. The brace counter would be fooled by a brace
|
|
180
|
+
* inside a string literal, but the full-body comparison catches that case
|
|
181
|
+
* too: a mis-delimited body cannot match the original.
|
|
182
|
+
*
|
|
183
|
+
* @param text - The contents of the SDK's `wallets_manager.cpp`.
|
|
184
|
+
* @returns The patched contents. Already-patched input comes back
|
|
185
|
+
* unchanged, so the caller does not need to track whether it ran.
|
|
186
|
+
*/
|
|
187
|
+
function patchCloseWallet(text) {
|
|
188
|
+
if (text.includes(patchMarker))
|
|
189
|
+
return text;
|
|
190
|
+
const anchor = 'std::string wallets_manager::close_wallet(size_t wallet_id)';
|
|
191
|
+
const start = text.indexOf(anchor);
|
|
192
|
+
if (start < 0 || text.includes(anchor, start + 1)) {
|
|
193
|
+
throw new Error(`Cannot find a unique wallets_manager::close_wallet to patch. ${hint}`);
|
|
194
|
+
}
|
|
195
|
+
// Take the whole function by brace balance:
|
|
196
|
+
let depth = 0;
|
|
197
|
+
let end = -1;
|
|
198
|
+
for (let i = text.indexOf('{', start); i >= 0 && i < text.length; ++i) {
|
|
199
|
+
if (text[i] === '{')
|
|
200
|
+
++depth;
|
|
201
|
+
if (text[i] === '}' && --depth === 0) {
|
|
202
|
+
end = i + 1;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (end < 0) {
|
|
207
|
+
throw new Error(`Cannot delimit the body of wallets_manager::close_wallet. ${hint}`);
|
|
208
|
+
}
|
|
209
|
+
if (normalize(text.slice(start, end)) !== normalize(original)) {
|
|
210
|
+
throw new Error('wallets_manager::close_wallet does not match the pinned original ' +
|
|
211
|
+
`this patch was written against. ${hint}`);
|
|
212
|
+
}
|
|
213
|
+
return text.slice(0, start) + replacement + text.slice(end);
|
|
214
|
+
}
|
|
215
|
+
exports.patchCloseWallet = patchCloseWallet;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
export declare const tmpPath: string;
|
|
3
|
+
/**
|
|
4
|
+
* Fetches a git repo and checks out a hash.
|
|
5
|
+
*
|
|
6
|
+
* We fetch just the pinned commit rather than cloning the whole history.
|
|
7
|
+
* Some of these repos keep large prebuilt binaries in Git LFS, so a plain
|
|
8
|
+
* clone drags down every historical revision of those - slow, and prone to
|
|
9
|
+
* dying partway through on a flaky connection.
|
|
10
|
+
*
|
|
11
|
+
* Pass `lfsIncludes` for LFS repos to grab only the paths we build against,
|
|
12
|
+
* skipping the platforms we never touch.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getRepo(name: string, uri: string, hash: string, opts?: {
|
|
15
|
+
lfsIncludes?: string[];
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Downloads & unpacks a zip file.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getZip(name: string, uri: string): Promise<void>;
|
|
21
|
+
export declare function fileExists(path: string): Promise<boolean>;
|
|
22
|
+
export declare function loudExec(command: string, args: string[], opts?: {
|
|
23
|
+
cwd?: string;
|
|
24
|
+
env?: NodeJS.ProcessEnv;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Runs a command and returns its stdout, without going through a shell.
|
|
28
|
+
*/
|
|
29
|
+
export declare function captureExec(command: string, args: string[], opts?: {
|
|
30
|
+
cwd?: string;
|
|
31
|
+
}): Promise<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Runs a command and returns its results.
|
|
34
|
+
*/
|
|
35
|
+
export declare function quietExec(command: string, args: string[], opts?: {
|
|
36
|
+
cwd?: string;
|
|
37
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.quietExec = exports.captureExec = exports.loudExec = exports.fileExists = exports.getZip = exports.getRepo = exports.tmpPath = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const promises_1 = require("fs/promises");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
exports.tmpPath = (0, path_1.join)(__dirname, '../../tmp');
|
|
8
|
+
/**
|
|
9
|
+
* Fetches a git repo and checks out a hash.
|
|
10
|
+
*
|
|
11
|
+
* We fetch just the pinned commit rather than cloning the whole history.
|
|
12
|
+
* Some of these repos keep large prebuilt binaries in Git LFS, so a plain
|
|
13
|
+
* clone drags down every historical revision of those - slow, and prone to
|
|
14
|
+
* dying partway through on a flaky connection.
|
|
15
|
+
*
|
|
16
|
+
* Pass `lfsIncludes` for LFS repos to grab only the paths we build against,
|
|
17
|
+
* skipping the platforms we never touch.
|
|
18
|
+
*/
|
|
19
|
+
async function getRepo(name, uri, hash, opts = {}) {
|
|
20
|
+
const { lfsIncludes } = opts;
|
|
21
|
+
const path = (0, path_1.join)(exports.tmpPath, name);
|
|
22
|
+
// Set up the repo. Both steps are idempotent and run every time rather
|
|
23
|
+
// than behind a `fileExists(path)` check: a run killed between the mkdir
|
|
24
|
+
// and the init used to leave a directory that looked set up but had no
|
|
25
|
+
// `.git` and no remote, which every later run then skipped and no fetch
|
|
26
|
+
// could recover from.
|
|
27
|
+
if (!(await fileExists((0, path_1.join)(path, '.git'))))
|
|
28
|
+
console.log(`Creating ${name}...`);
|
|
29
|
+
await (0, promises_1.mkdir)(path, { recursive: true });
|
|
30
|
+
await loudExec('git', ['init', '-q'], { cwd: path });
|
|
31
|
+
if (!(await tryExec('git', ['remote', 'set-url', 'origin', uri], path))) {
|
|
32
|
+
await loudExec('git', ['remote', 'add', 'origin', uri], { cwd: path });
|
|
33
|
+
}
|
|
34
|
+
// Fetch the pinned commit, unless we already have it.
|
|
35
|
+
// The check also picks up hash bumps on an existing checkout:
|
|
36
|
+
if (!(await hasCommit(path, hash))) {
|
|
37
|
+
console.log(`Fetching ${name}...`);
|
|
38
|
+
try {
|
|
39
|
+
await loudExec('git', ['fetch', '--depth', '1', 'origin', hash], {
|
|
40
|
+
cwd: path
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// Not every server allows fetching a bare hash. Revoked access, a
|
|
45
|
+
// dropped connection and a force-pushed-away commit land here too, and
|
|
46
|
+
// if the full fetch succeeds without bringing the hash along, the
|
|
47
|
+
// checkout below fails on an unrelated-looking "unknown revision" --
|
|
48
|
+
// so say what actually went wrong first:
|
|
49
|
+
console.log(`Could not fetch ${hash} directly, retrying with a full fetch: ${String(error)}`);
|
|
50
|
+
// A plain `fetch` on a repo an earlier run left shallow keeps it
|
|
51
|
+
// shallow, which brings down the branch tips and not the pinned commit
|
|
52
|
+
// behind them. `--unshallow` is only valid on a shallow repo, so pick
|
|
53
|
+
// by whether git left its marker:
|
|
54
|
+
const shallow = await fileExists((0, path_1.join)(path, '.git', 'shallow'));
|
|
55
|
+
await loudExec('git', shallow ? ['fetch', '--unshallow', 'origin'] : ['fetch', 'origin'], { cwd: path });
|
|
56
|
+
}
|
|
57
|
+
// Fail here rather than at the checkout, which reports a missing pin as
|
|
58
|
+
// an opaque "unknown revision" with the fetch error already gone:
|
|
59
|
+
if (!(await hasCommit(path, hash))) {
|
|
60
|
+
throw new Error(`Could not fetch ${hash} for ${name} from ${uri}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Checkout. Skipping the LFS smudge filter keeps this from blocking on an
|
|
64
|
+
// all-platforms LFS download; we pull the parts we need below:
|
|
65
|
+
console.log(`Checking out ${name}...`);
|
|
66
|
+
await loudExec('git', ['checkout', '-f', hash], {
|
|
67
|
+
cwd: path,
|
|
68
|
+
env: { ...process.env, GIT_LFS_SKIP_SMUDGE: '1' }
|
|
69
|
+
});
|
|
70
|
+
// Grab the LFS objects we actually build against. The empty `--exclude` is
|
|
71
|
+
// load-bearing: `--include` overrides `lfs.fetchinclude` but leaves any
|
|
72
|
+
// configured `lfs.fetchexclude` in force, so on a machine with a global
|
|
73
|
+
// exclude the pull reports success while leaving pointer files in place,
|
|
74
|
+
// and the build then links against 4KB of ASCII.
|
|
75
|
+
if (lfsIncludes != null) {
|
|
76
|
+
console.log(`Fetching ${name} LFS objects...`);
|
|
77
|
+
await loudExec('git', ['lfs', 'pull', `--include=${lfsIncludes.join(',')}`, '--exclude='], { cwd: path });
|
|
78
|
+
}
|
|
79
|
+
// Checkout submodules. `--force` because `update-sources` patches
|
|
80
|
+
// `RIPEMD160.h`, which lives inside the Zano submodule: without it the
|
|
81
|
+
// second run of this script aborts on "local changes would be overwritten"
|
|
82
|
+
// and the build is not repeatable, which `prepack` needs it to be.
|
|
83
|
+
await loudExec('git', ['submodule', 'update', '--init', '--recursive', '--force'], { cwd: path });
|
|
84
|
+
}
|
|
85
|
+
exports.getRepo = getRepo;
|
|
86
|
+
/**
|
|
87
|
+
* Checks whether a repo already contains a particular commit, and everything
|
|
88
|
+
* that commit points at.
|
|
89
|
+
*
|
|
90
|
+
* `rev-parse --verify` would accept any well-formed hash without checking
|
|
91
|
+
* that we have it, and `cat-file -e <hash>` proves only that the commit
|
|
92
|
+
* object arrived. Below `fetch.unpackLimit` git writes loose objects in no
|
|
93
|
+
* particular order, so a run killed mid-unpack can leave the commit present
|
|
94
|
+
* with its tree missing -- which this check would wave through, skipping the
|
|
95
|
+
* fetch that would repair it. `rev-list --objects` walks the whole thing.
|
|
96
|
+
*/
|
|
97
|
+
async function hasCommit(path, hash) {
|
|
98
|
+
return await tryExec('git', ['rev-list', '--objects', '--quiet', hash, '--'], path);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Runs a command, reporting whether it succeeded rather than throwing, and
|
|
102
|
+
* without inheriting stdio -- for the cases where a failure is an expected
|
|
103
|
+
* answer instead of an error.
|
|
104
|
+
*/
|
|
105
|
+
async function tryExec(command, args, cwd) {
|
|
106
|
+
return await new Promise(resolve => {
|
|
107
|
+
const child = (0, child_process_1.spawn)(command, args, { cwd, stdio: 'ignore' });
|
|
108
|
+
child.on('error', () => resolve(false));
|
|
109
|
+
child.on('exit', code => resolve(code === 0));
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Downloads & unpacks a zip file.
|
|
114
|
+
*/
|
|
115
|
+
async function getZip(name, uri) {
|
|
116
|
+
const path = (0, path_1.join)(exports.tmpPath, name);
|
|
117
|
+
if (!(await fileExists(path))) {
|
|
118
|
+
console.log(`Getting ${name}...`);
|
|
119
|
+
await loudExec('curl', ['-L', '-o', path, uri]);
|
|
120
|
+
}
|
|
121
|
+
// Unzip:
|
|
122
|
+
await loudExec('unzip', ['-u', path]);
|
|
123
|
+
}
|
|
124
|
+
exports.getZip = getZip;
|
|
125
|
+
async function fileExists(path) {
|
|
126
|
+
return await (0, promises_1.access)(path).then(() => true, () => false);
|
|
127
|
+
}
|
|
128
|
+
exports.fileExists = fileExists;
|
|
129
|
+
async function loudExec(command, args, opts = {}) {
|
|
130
|
+
const { cwd = exports.tmpPath, env = process.env } = opts;
|
|
131
|
+
return await new Promise((resolve, reject) => {
|
|
132
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
133
|
+
cwd,
|
|
134
|
+
stdio: 'inherit',
|
|
135
|
+
env
|
|
136
|
+
});
|
|
137
|
+
child.on('error', reject);
|
|
138
|
+
child.on('exit', code => {
|
|
139
|
+
if (code === 0) {
|
|
140
|
+
resolve();
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
reject(new Error(`${command} exited with code ${String(code)}`));
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
exports.loudExec = loudExec;
|
|
149
|
+
/**
|
|
150
|
+
* Runs a command and returns its stdout, without going through a shell.
|
|
151
|
+
*/
|
|
152
|
+
async function captureExec(command, args, opts = {}) {
|
|
153
|
+
const { cwd = exports.tmpPath } = opts;
|
|
154
|
+
return await new Promise((resolve, reject) => {
|
|
155
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
156
|
+
cwd,
|
|
157
|
+
stdio: ['ignore', 'pipe', 'inherit']
|
|
158
|
+
});
|
|
159
|
+
let out = '';
|
|
160
|
+
child.stdout.on('data', chunk => {
|
|
161
|
+
out += String(chunk);
|
|
162
|
+
});
|
|
163
|
+
child.on('error', reject);
|
|
164
|
+
// `close` rather than `exit`: the process can exit while stdout still has
|
|
165
|
+
// buffered data, which would truncate the output we are here to collect.
|
|
166
|
+
// `close` waits for the stdio streams too.
|
|
167
|
+
child.on('close', code => {
|
|
168
|
+
if (code === 0)
|
|
169
|
+
resolve(out.replace(/\n$/, ''));
|
|
170
|
+
else
|
|
171
|
+
reject(new Error(`${command} exited with code ${String(code)}`));
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
exports.captureExec = captureExec;
|
|
176
|
+
/**
|
|
177
|
+
* Runs a command and returns its results.
|
|
178
|
+
*/
|
|
179
|
+
async function quietExec(command, args, opts = {}) {
|
|
180
|
+
const { cwd = exports.tmpPath } = opts;
|
|
181
|
+
return (0, child_process_1.execSync)(command + ' ' + args.join(' '), {
|
|
182
|
+
cwd,
|
|
183
|
+
encoding: 'utf8'
|
|
184
|
+
}).replace(/\n$/, '');
|
|
185
|
+
}
|
|
186
|
+
exports.quietExec = quietExec;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds llvm-objcopy in Homebrew.
|
|
3
|
+
*
|
|
4
|
+
* Homebrew doesn't symlink LLVM into the normal location,
|
|
5
|
+
* to avoid conflicting with Xcode's built-in tools.
|
|
6
|
+
* We can get around this by looking in the right places.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getObjcopyPath(): Promise<string>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getObjcopyPath = void 0;
|
|
4
|
+
const common_1 = require("./common");
|
|
5
|
+
/**
|
|
6
|
+
* Finds llvm-objcopy in Homebrew.
|
|
7
|
+
*
|
|
8
|
+
* Homebrew doesn't symlink LLVM into the normal location,
|
|
9
|
+
* to avoid conflicting with Xcode's built-in tools.
|
|
10
|
+
* We can get around this by looking in the right places.
|
|
11
|
+
*/
|
|
12
|
+
async function getObjcopyPath() {
|
|
13
|
+
const whichPath = await (0, common_1.quietExec)('which', ['llvm-objcopy']).catch(() => { });
|
|
14
|
+
if (whichPath != null)
|
|
15
|
+
return whichPath;
|
|
16
|
+
const paths = [
|
|
17
|
+
'/opt/homebrew/opt/llvm/bin/llvm-objcopy',
|
|
18
|
+
'/usr/local/opt/llvm/bin/llvm-objcopy' // for Intel Macs
|
|
19
|
+
];
|
|
20
|
+
for (const path of paths) {
|
|
21
|
+
if (await (0, common_1.fileExists)(path))
|
|
22
|
+
return path;
|
|
23
|
+
}
|
|
24
|
+
throw new Error('Please install `llvm-objcopy` using `brew install llvm`.');
|
|
25
|
+
}
|
|
26
|
+
exports.getObjcopyPath = getObjcopyPath;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directories the Zano SDK creates under the working directory the app hands
|
|
3
|
+
* it, and that `ios/ZanoModule.mm` therefore has to pre-create so it can
|
|
4
|
+
* exclude them from device backups -- the wallet files hold the seed and
|
|
5
|
+
* spend keys.
|
|
6
|
+
*
|
|
7
|
+
* Keep this in sync with the `prepareZanoDirectory` calls in
|
|
8
|
+
* `ios/ZanoModule.mm`. `update-sources.ts` fails the build if the SDK
|
|
9
|
+
* declares a directory that is missing here.
|
|
10
|
+
*/
|
|
11
|
+
export declare const sdkFolders: string[];
|
|
12
|
+
/**
|
|
13
|
+
* Extracts the directory names the SDK declares, sorted.
|
|
14
|
+
*
|
|
15
|
+
* Matches `#define`s whose name mentions a folder, so `APP_CONFIG_FOLDER`
|
|
16
|
+
* and `WALLETS_FOLDER_NAME` count but `APP_CONFIG_FILENAME` does not.
|
|
17
|
+
*
|
|
18
|
+
* This catches directories declared as `#define`d names, which is how the
|
|
19
|
+
* SDK has always declared them. It cannot catch a path composed inline, so
|
|
20
|
+
* it is a tripwire rather than a proof.
|
|
21
|
+
*
|
|
22
|
+
* @param apiText - The contents of the SDK's `plain_wallet_api.cpp`.
|
|
23
|
+
* @throws If nothing matches at all, which means the SDK changed how it
|
|
24
|
+
* declares directories and this check needs rewriting rather than silently
|
|
25
|
+
* reporting "no new folders".
|
|
26
|
+
*/
|
|
27
|
+
export declare function findSdkFolders(apiText: string): string[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findSdkFolders = exports.sdkFolders = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Directories the Zano SDK creates under the working directory the app hands
|
|
6
|
+
* it, and that `ios/ZanoModule.mm` therefore has to pre-create so it can
|
|
7
|
+
* exclude them from device backups -- the wallet files hold the seed and
|
|
8
|
+
* spend keys.
|
|
9
|
+
*
|
|
10
|
+
* Keep this in sync with the `prepareZanoDirectory` calls in
|
|
11
|
+
* `ios/ZanoModule.mm`. `update-sources.ts` fails the build if the SDK
|
|
12
|
+
* declares a directory that is missing here.
|
|
13
|
+
*/
|
|
14
|
+
exports.sdkFolders = ['app_config', 'logs', 'wallets'];
|
|
15
|
+
/**
|
|
16
|
+
* Extracts the directory names the SDK declares, sorted.
|
|
17
|
+
*
|
|
18
|
+
* Matches `#define`s whose name mentions a folder, so `APP_CONFIG_FOLDER`
|
|
19
|
+
* and `WALLETS_FOLDER_NAME` count but `APP_CONFIG_FILENAME` does not.
|
|
20
|
+
*
|
|
21
|
+
* This catches directories declared as `#define`d names, which is how the
|
|
22
|
+
* SDK has always declared them. It cannot catch a path composed inline, so
|
|
23
|
+
* it is a tripwire rather than a proof.
|
|
24
|
+
*
|
|
25
|
+
* @param apiText - The contents of the SDK's `plain_wallet_api.cpp`.
|
|
26
|
+
* @throws If nothing matches at all, which means the SDK changed how it
|
|
27
|
+
* declares directories and this check needs rewriting rather than silently
|
|
28
|
+
* reporting "no new folders".
|
|
29
|
+
*/
|
|
30
|
+
function findSdkFolders(apiText) {
|
|
31
|
+
const found = new Set();
|
|
32
|
+
const regexp = /^#define\s+\w*(?:FOLDER|_DIR)\w*\s+"([^"]*)"/gm;
|
|
33
|
+
let match;
|
|
34
|
+
while ((match = regexp.exec(apiText)) != null) {
|
|
35
|
+
found.add(match[1]);
|
|
36
|
+
}
|
|
37
|
+
if (found.size === 0) {
|
|
38
|
+
throw new Error('Found no folder definitions in plain_wallet_api.cpp. The SDK has ' +
|
|
39
|
+
'probably changed how it declares them, so this check needs updating.');
|
|
40
|
+
}
|
|
41
|
+
return [...found].sort((a, b) => a.localeCompare(b));
|
|
42
|
+
}
|
|
43
|
+
exports.findSdkFolders = findSdkFolders;
|