Cleanup build, no sed mangling (#129)

* Polyfill require adjust

* Assert in promise for getRandomBytes

* Adjust jest

* Revert explicit overrides (from base)

* use typeof

* Re-add crypto-polyfill (backwards, direct imports)

* crypto log

* Fix dev

* Adjust

* Cleanups, no sed mangling

* Cleanup build folder

* Remove exports

* Cleanups

* Adjust env vs util

* Remove Buffer alloc

* Cleant ext vs int

* Rework naming

* Mapping rename

* Combine index & exports

* withWasm wrapper

* pass{String, U8a} -> alloc{String, U8a}

* Internal reuse

* Explict package-only test
This commit is contained in:
Jaco Greeff
2020-11-02 16:39:54 +01:00
committed by GitHub
parent 2ac68bdb18
commit da946b18ce
18 changed files with 422 additions and 241 deletions

View File

@@ -13,4 +13,5 @@ jobs:
- name: ${{ matrix.step }}
run: |
yarn install --immutable | grep -v 'YN0013'
./scripts/install-build-deps.sh
yarn ${{ matrix.step }}

View File

@@ -24,6 +24,7 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
yarn install --immutable | grep -v 'YN0013'
./scripts/install-build-deps.sh
yarn ${{ matrix.step }}
dummy:

View File

@@ -12,7 +12,7 @@
},
"scripts": {
"build": "./scripts/build.sh",
"build:release": "./scripts/build-release.sh",
"build:release": "polkadot-ci-ghact-build",
"lint": "polkadot-dev-run-lint",
"clean": "./scripts/clean.sh",
"postinstall": "polkadot-dev-yarn-only",

View File

@@ -2,18 +2,6 @@
"name": "@polkadot/wasm-crypto",
"version": "1.5.0-beta.14",
"author": "Jaco Greeff <jacogr@gmail.com>",
"files": [
"crypto-polyfill.js",
"exports.js",
"index.d.ts",
"index.js",
"wasm.d.ts",
"wasm.js",
"wasm_asm.js",
"wasm_asm_stub.js",
"wasm_promise.js",
"wasm_wasm.js"
],
"react-native": {
"./wasm_asm_stub.js": "./wasm_asm.js"
},

View File

@@ -151,7 +151,7 @@ pub fn ext_scrypt(password: &[u8], salt: &[u8], log2_n: u8, r: u32, p: u32) -> V
///
/// * data: Arbitrary data to be hashed
///
/// Returns a vecor with the hash result
/// Returns a vector with the hash result
#[wasm_bindgen]
pub fn ext_sha512(data: &[u8]) -> Vec<u8> {
let mut hasher = Sha512::new();
@@ -168,7 +168,7 @@ pub fn ext_sha512(data: &[u8]) -> Vec<u8> {
/// * data: Arbitrary data to be hashed
/// * rounds: Number of 8-byte rounds to add to the output
///
/// Returns a vecor with the hash result
/// Returns a vector with the hash result
#[wasm_bindgen]
pub fn ext_twox(data: &[u8], rounds: u32) -> Vec<u8> {
let mut vec = vec![];
@@ -186,7 +186,7 @@ pub mod tests {
use hex_literal::hex;
use super::*;
// // Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign.
// // Constructs the message that Ethereum RPCs `personal_sign` and `eth_sign` would sign.
// fn ethereum_signable_message(data: &[u8]) -> Vec<u8> {
// let prefix = b"Pay DOTs to the Polkadot account:";
// let mut l = prefix.len() + data.len();

View File

@@ -0,0 +1,105 @@
// Copyright 2019-2020 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
const { assert, stringToU8a, u8aToString } = require('@polkadot/util');
const pkg = require('./package.json');
let wasm;
let cachegetInt32 = null;
let cachegetUint8 = null;
async function initWasm (wasmBytes, asmFallback, wbg) {
try {
assert(typeof WebAssembly !== 'undefined', 'WebAssembly is not available in your environment');
const source = await WebAssembly.instantiate(wasmBytes, { wbg });
wasm = source.instance.exports;
} catch (error) {
// if we have a valid supplied asm.js, return that
if (asmFallback && asmFallback.ext_blake2b) {
wasm = asmFallback;
} else {
console.error(`ERROR: Unable to initialize ${pkg.name} ${pkg.version}`);
console.error(error);
wasm = null;
}
}
}
function withWasm (fn) {
return (...params) => {
assert(wasm, 'The WASM interface has not been initialized. Ensure that you wait for the initialization Promise with waitReady() from @polkadot/wasm-crypto (or cryptoWaitReady() from @polkadot/util-crypto) before attempting to use WASM-only interfaces.');
return fn(wasm, ...params);
};
}
function getWasm () {
return wasm;
}
function getInt32 () {
if (cachegetInt32 === null || cachegetInt32.buffer !== wasm.memory.buffer) {
cachegetInt32 = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32;
}
function getUint8 () {
if (cachegetUint8 === null || cachegetUint8.buffer !== wasm.memory.buffer) {
cachegetUint8 = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8;
}
function getU8a (ptr, len) {
return getUint8().subarray(ptr / 1, ptr / 1 + len);
}
function getString (ptr, len) {
return u8aToString(getU8a(ptr, len));
}
function allocU8a (arg) {
const ptr = wasm.__wbindgen_malloc(arg.length * 1);
getUint8().set(arg, ptr / 1);
return [ptr, arg.length];
}
function allocString (arg) {
return allocU8a(stringToU8a(arg));
}
function resultU8a () {
const r0 = getInt32()[8 / 4 + 0];
const r1 = getInt32()[8 / 4 + 1];
const ret = getU8a(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return ret;
}
function resultString () {
return u8aToString(resultU8a());
}
module.exports = {
allocString,
allocU8a,
getInt32,
getString,
getU8a,
getWasm,
initWasm,
resultString,
resultU8a,
withWasm
};

View File

@@ -1,20 +0,0 @@
// Copyright 2019-2020 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
const crypto = require('crypto');
if (!global.crypto) {
global.crypto = {};
}
if (!global.crypto.getRandomValues) {
global.crypto.getRandomValues = function (arr) {
const buffer = crypto.randomBytes(arr.length);
return buffer.reduce((arr, value, index) => {
arr[index] = value;
return arr;
}, arr);
};
}

View File

@@ -1,50 +0,0 @@
// Copyright 2019-2020 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/* eslint-disable sort-keys */
const { assert } = require('@polkadot/util');
const INIT_ERRROR = 'The WASM interface has not been initialized. Ensure that you wait for the initialization Promise with waitReady() from @polkadot/wasm-crypto (or cryptoWaitReady() from @polkadot/util-crypto) before attempting to use WASM-only interfaces.';
module.exports = function (stubbed) {
const wrapReady = (fn) =>
(...params) => {
assert(stubbed.isReady(), INIT_ERRROR);
return fn(...params);
};
return {
bip39Generate: wrapReady(stubbed.ext_bip39_generate),
bip39ToEntropy: wrapReady(stubbed.ext_bip39_to_entropy),
bip39ToMiniSecret: wrapReady(stubbed.ext_bip39_to_mini_secret),
bip39ToSeed: wrapReady(stubbed.ext_bip39_to_seed),
bip39Validate: wrapReady(stubbed.ext_bip39_validate),
ed25519KeypairFromSeed: wrapReady(stubbed.ext_ed_from_seed),
ed25519Sign: wrapReady(stubbed.ext_ed_sign),
ed25519Verify: wrapReady(stubbed.ext_ed_verify),
sr25519DeriveKeypairHard: wrapReady(stubbed.ext_sr_derive_keypair_hard),
sr25519DeriveKeypairSoft: wrapReady(stubbed.ext_sr_derive_keypair_soft),
sr25519DerivePublicSoft: wrapReady(stubbed.ext_sr_derive_public_soft),
sr25519KeypairFromSeed: wrapReady(stubbed.ext_sr_from_seed),
sr25519Sign: wrapReady(stubbed.ext_sr_sign),
sr25519Verify: wrapReady(stubbed.ext_sr_verify),
blake2b: wrapReady(stubbed.ext_blake2b),
keccak256: wrapReady(stubbed.ext_keccak256),
pbkdf2: wrapReady(stubbed.ext_pbkdf2),
scrypt: wrapReady(stubbed.ext_scrypt),
// secp256k1IsRecoverable: wrapReady(stubbed.ext_secp256k1_is_recoverable);
// secp256k1Recover: wrapReady(stubbed.ext_secp256k1_recover);
sha512: wrapReady(stubbed.ext_sha512),
twox: wrapReady(stubbed.ext_twox),
isReady: stubbed.isReady,
waitReady: stubbed.waitReady
};
};

View File

@@ -0,0 +1,104 @@
// Copyright 2019-2020 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
const crypto = require('crypto');
const { getString, getU8a, getWasm } = require('./bridge');
const requires = { crypto };
const heap = new Array(32).fill(undefined).concat(undefined, null, true, false);
let heapNext = heap.length;
// FIXME We really want to get rid of this polyfill completely
if (!global.crypto) {
global.crypto = {};
}
if (!global.crypto.getRandomValues) {
global.crypto.getRandomValues = function (arr) {
return crypto.randomBytes(arr.length).reduce((arr, value, index) => {
arr[index] = value;
return arr;
}, arr);
};
}
function getObject (idx) {
return heap[idx];
}
function dropObject (idx) {
if (idx < 36) {
return;
}
heap[idx] = heapNext;
heapNext = idx;
}
function takeObject (idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
function addObject (obj) {
if (heapNext === heap.length) {
heap.push(heap.length + 1);
}
const idx = heapNext;
heapNext = heap[idx];
heap[idx] = obj;
return idx;
}
function handleError (f) {
return function () {
try {
return f.apply(this, arguments);
} catch (e) {
getWasm().__wbindgen_exn_store(addObject(e));
}
};
}
module.exports.__wbindgen_is_undefined = function (arg0) {
return getObject(arg0) === undefined;
};
module.exports.__wbg_self_1b7a39e3a92c949c = handleError(() => addObject(self.self));
module.exports.__wbg_require_604837428532a733 = function (arg0, arg1) {
return addObject(requires[getString(arg0, arg1)]);
};
module.exports.__wbg_crypto_968f1772287e2df0 = function (arg0) {
return addObject(getObject(arg0).crypto);
};
module.exports.__wbg_getRandomValues_a3d34b4fee3c2869 = function (arg0) {
return addObject(getObject(arg0).getRandomValues);
};
module.exports.__wbg_getRandomValues_f5e14ab7ac8e995d = function (arg0, arg1, arg2) {
getObject(arg0).getRandomValues(getU8a(arg1, arg2));
};
module.exports.__wbg_randomFillSync_d5bd2d655fdf256a = function (arg0, arg1, arg2) {
getObject(arg0).randomFillSync(getU8a(arg1, arg2));
};
module.exports.__wbindgen_object_drop_ref = function (arg0) {
takeObject(arg0);
};
module.exports.abort = function () {
throw new Error('abort');
};

View File

@@ -1,6 +1,188 @@
// Copyright 2019-2020 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
const stubbed = require('./wasm');
const { allocString, allocU8a, getWasm, initWasm, resultString, resultU8a, withWasm } = require('./bridge');
const imports = require('./imports');
const asmFallback = require('./wasm_asm_stub');
const wasmBytes = require('./wasm_wasm');
module.exports = require('./exports')(stubbed);
const wasmPromise = initWasm(wasmBytes, asmFallback, imports).catch(() => null);
module.exports.bip39Generate = withWasm((wasm, words) => {
wasm.ext_bip39_generate(8, words);
return resultString();
});
module.exports.bip39ToEntropy = withWasm((wasm, phrase) => {
const [ptr0, len0] = allocString(phrase);
wasm.ext_bip39_to_entropy(8, ptr0, len0);
return resultU8a();
});
module.exports.bip39ToMiniSecret = withWasm((wasm, phrase, password) => {
const [ptr0, len0] = allocString(phrase);
const [ptr1, len1] = allocString(password);
wasm.ext_bip39_to_mini_secret(8, ptr0, len0, ptr1, len1);
return resultU8a();
});
module.exports.bip39ToSeed = withWasm((wasm, phrase, password) => {
const [ptr0, len0] = allocString(phrase);
const [ptr1, len1] = allocString(password);
wasm.ext_bip39_to_seed(8, ptr0, len0, ptr1, len1);
return resultU8a();
});
module.exports.bip39Validate = withWasm((wasm, phrase) => {
const [ptr0, len0] = allocString(phrase);
const ret = wasm.ext_bip39_validate(ptr0, len0);
return ret !== 0;
});
module.exports.ed25519KeypairFromSeed = withWasm((wasm, seed) => {
const [ptr0, len0] = allocU8a(seed);
wasm.ext_ed_from_seed(8, ptr0, len0);
return resultU8a();
});
module.exports.ed25519Sign = withWasm((wasm, pubkey, seckey, message) => {
const [ptr0, len0] = allocU8a(pubkey);
const [ptr1, len1] = allocU8a(seckey);
const [ptr2, len2] = allocU8a(message);
wasm.ext_ed_sign(8, ptr0, len0, ptr1, len1, ptr2, len2);
return resultU8a();
});
module.exports.ed25519Verify = withWasm((wasm, signature, message, pubkey) => {
const [ptr0, len0] = allocU8a(signature);
const [ptr1, len1] = allocU8a(message);
const [ptr2, len2] = allocU8a(pubkey);
const ret = wasm.ext_ed_verify(ptr0, len0, ptr1, len1, ptr2, len2);
return ret !== 0;
});
module.exports.blake2b = withWasm((wasm, data, key, size) => {
const [ptr0, len0] = allocU8a(data);
const [ptr1, len1] = allocU8a(key);
wasm.ext_blake2b(8, ptr0, len0, ptr1, len1, size);
return resultU8a();
});
module.exports.keccak256 = withWasm((wasm, data) => {
const [ptr0, len0] = allocU8a(data);
wasm.ext_keccak256(8, ptr0, len0);
return resultU8a();
});
module.exports.pbkdf2 = withWasm((wasm, data, salt, rounds) => {
const [ptr0, len0] = allocU8a(data);
const [ptr1, len1] = allocU8a(salt);
wasm.ext_pbkdf2(8, ptr0, len0, ptr1, len1, rounds);
return resultU8a();
});
module.exports.scrypt = withWasm((wasm, password, salt, log2n, r, p) => {
const [ptr0, len0] = allocU8a(password);
const [ptr1, len1] = allocU8a(salt);
wasm.ext_scrypt(8, ptr0, len0, ptr1, len1, log2n, r, p);
return resultU8a();
});
module.exports.sha512 = withWasm((wasm, data) => {
const [ptr0, len0] = allocU8a(data);
wasm.ext_sha512(8, ptr0, len0);
return resultU8a();
});
module.exports.twox = withWasm((wasm, data, rounds) => {
const [ptr0, len0] = allocU8a(data);
wasm.ext_twox(8, ptr0, len0, rounds);
return resultU8a();
});
module.exports.sr25519DeriveKeypairHard = withWasm((wasm, pair, cc) => {
const [ptr0, len0] = allocU8a(pair);
const [ptr1, len1] = allocU8a(cc);
wasm.ext_sr_derive_keypair_hard(8, ptr0, len0, ptr1, len1);
return resultU8a();
});
module.exports.sr25519DeriveKeypairSoft = withWasm((wasm, pair, cc) => {
const [ptr0, len0] = allocU8a(pair);
const [ptr1, len1] = allocU8a(cc);
wasm.ext_sr_derive_keypair_soft(8, ptr0, len0, ptr1, len1);
return resultU8a();
});
module.exports.sr25519DerivePublicSoft = withWasm((wasm, pubkey, cc) => {
const [ptr0, len0] = allocU8a(pubkey);
const [ptr1, len1] = allocU8a(cc);
wasm.ext_sr_derive_public_soft(8, ptr0, len0, ptr1, len1);
return resultU8a();
});
module.exports.sr25519KeypairFromSeed = withWasm((wasm, seed) => {
const [ptr0, len0] = allocU8a(seed);
wasm.ext_sr_from_seed(8, ptr0, len0);
return resultU8a();
});
module.exports.sr25519Sign = withWasm((wasm, pubkey, secret, message) => {
const [ptr0, len0] = allocU8a(pubkey);
const [ptr1, len1] = allocU8a(secret);
const [ptr2, len2] = allocU8a(message);
wasm.ext_sr_sign(8, ptr0, len0, ptr1, len1, ptr2, len2);
return resultU8a();
});
module.exports.sr25519Verify = withWasm((wasm, signature, message, pubkey) => {
const [ptr0, len0] = allocU8a(signature);
const [ptr1, len1] = allocU8a(message);
const [ptr2, len2] = allocU8a(pubkey);
const ret = wasm.ext_sr_verify(ptr0, len0, ptr1, len1, ptr2, len2);
return ret !== 0;
});
module.exports.isReady = function () {
return !!getWasm();
};
module.exports.waitReady = function () {
return wasmPromise.then(() => !!getWasm());
};

View File

@@ -1,27 +0,0 @@
// Copyright 2019-2020 @polkadot/wasm-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
require('./crypto-polyfill');
const pkg = require('./package.json');
const asm = require('./wasm_asm_stub');
const bytes = require('./wasm_wasm');
const imports = require('./wasm');
module.exports = async function createExportPromise () {
try {
const { instance } = await WebAssembly.instantiate(bytes, { __wbindgen_placeholder__: imports });
return instance.exports;
} catch (error) {
// if we have a valid supplied asm.js, return that
if (asm && asm.ext_blake2b) {
return asm;
}
console.error(`ERROR: Unable to initialize ${pkg.name} ${pkg.version}`);
console.error(error);
return null;
}
};

View File

@@ -5,11 +5,8 @@
set -e
BGJ=build/wasm_bg.js
SRC_WASM=build/wasm.js
DEF=build/wasm.d.ts
WSM=build/wasm_bg.wasm
OPT=build/wasm_opt.wasm
WSM=pkg/wasm_bg.wasm
OPT=pkg/wasm_opt.wasm
ASM=build/wasm_asm.js
echo "*** Building package"
@@ -17,11 +14,11 @@ echo "*** Building package"
# cleanup old
echo "*** Cleaning old builds"
rm -rf ./build ./pkg
mkdir -p build
# build new via wasm-pack
echo "*** Building WASM output"
wasm-pack build --release --scope polkadot --target nodejs
mv pkg build
wasm-pack build --release --scope polkadot --target web
# optimise
echo "*** Optimising WASM output"
@@ -37,65 +34,12 @@ echo "*** Building asm.js version"
# cleanup the generated asm, converting to cjs
sed -i -e '/import {/d' $ASM
echo "const imported = require('./wasm');
echo "const imports = require('./imports');
$(cat $ASM)" > $ASM
sed -i -e 's/{abort.*},memasmFunc/imported, memasmFunc/g' $ASM
sed -i -e 's/{abort.*},memasmFunc/imports, memasmFunc/g' $ASM
sed -i -e 's/export var /module\.exports\./g' $ASM
# copy our package interfaces
echo "*** Copying package sources"
cp package.json build/
cp src/js/* build/
echo "const crypto = require('crypto');
const { stringToU8a, u8aToString } = require('@polkadot/util');
const requires = { crypto };
$(cat $SRC_WASM)
" > $SRC_WASM
# whack comments
sed -i -e '/^\/\*\*/d' $SRC_WASM
sed -i -e '/^\*/d' $SRC_WASM
sed -i -e '/^\*\//d' $SRC_WASM
# we are swapping to a async interface for webpack support (wasm limits)
sed -i -e '/^wasm = require/d' $SRC_WASM
# We don't want inline requires
sed -i -e 's/ret = require(getStringFromWasm0(arg0, arg1));/ret = requires[getStringFromWasm0(arg0, arg1)];/g' $SRC_WASM
# this creates issues in both the browser and RN (@polkadot/util has a polyfill)
sed -i -e '/^const { TextEncoder } = require/d' $SRC_WASM
sed -i -e '/^let cachedTextEncoder = new /d' $SRC_WASM
sed -i -e 's/cachedTextEncoder\.encode/stringToU8a/g' $SRC_WASM
# this creates issues in both the browser and RN (@polkadot/util has a polyfill)
sed -i -e '/^const { TextDecoder } = require/d' $SRC_WASM
sed -i -e '/^let cachedTextDecoder = new/d' $SRC_WASM
sed -i -e 's/cachedTextDecoder\.decode/u8aToString/g' $SRC_WASM
# this is where we get the actual bg file
sed -i -e '/^const path = require/d' $SRC_WASM
sed -i -e '/^const bytes = require/d' $SRC_WASM
sed -i -e '/^const wasmModule =/d' $SRC_WASM
sed -i -e '/^const wasmInstance =/d' $SRC_WASM
sed -i -e '/^wasm = wasmInstance/d' $SRC_WASM
# construct our promise and add ready helpers (WASM)
echo "module.exports.abort = function () { throw new Error('abort'); };
const createPromise = require('./wasm_promise');
const wasmPromise = createPromise().catch(() => null);
module.exports.isReady = function () { return !!wasm; }
module.exports.waitReady = function () { return wasmPromise.then(() => !!wasm); }
wasmPromise.then((_wasm) => { wasm = _wasm });
" >> $SRC_WASM
# add extra methods to type definitions
echo "
export function isReady(): boolean;
export function waitReady(): Promise<boolean>;
" >> $DEF

View File

@@ -1,9 +0,0 @@
#!/usr/bin/env bash
# Copyright 2019-2020 @polkadot/wasm authors & contributors
# This software may be modified and distributed under the terms
# of the Apache-2.0 license. See the LICENSE file for details.
set -e
rustup toolchain install stable
yarn polkadot-ci-ghact-build

View File

@@ -5,27 +5,13 @@
set -e
rustup toolchain install stable
./scripts/install-build-deps.sh
echo "*** Building packages"
cd packages
cd packages/wasm-crypto
PACKAGES=( $(ls -1d *) )
../../scripts/build-package.sh
../../scripts/test-package.sh
for PKG in "${PACKAGES[@]}"; do
if [ -f "$PKG/package.json" ]; then
echo "*** Building $PKG"
cd $PKG
rm -rf build/*-e build/package.json build/README.md
ls -al build
../../scripts/build-package.sh
../../scripts/test-package.sh
rm -rf build/*-e build/package.json build/README.md
ls -al build
cd ..
fi
done
cd ..
cd ../..

View File

@@ -5,23 +5,10 @@
set -e
./scripts/install-build-deps.sh
echo "*** Cleaning packages"
cd packages
cd packages/wasm-crypto
PACKAGES=( $(ls -1d *) )
rm -rf build
cargo clean
for PKG in "${PACKAGES[@]}"; do
if [ -f "$PKG/package.json" ]; then
echo "*** Cleaning $PKG"
cd $PKG
rm -rf build
cargo clean
cd ..
fi
done
cd ..
cd ../..

View File

@@ -10,6 +10,8 @@ BINARYEN=( "wasm-opt" "wasm2js" )
unamestr=`uname`
rustup toolchain install stable
# install wasm-pack as required
if ! [ -x "$(command -v wasm-pack)" ]; then
echo "*** Installing wasm-pack"

View File

@@ -1,10 +1,9 @@
// Copyright 2019-2020 @polkadot/wasm authors & contributors
// SPDX-License-Identifier: Apache-2.0
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs');
const buffer = fs.readFileSync('./build/wasm_opt.wasm');
const buffer = fs.readFileSync('./pkg/wasm_opt.wasm');
fs.writeFileSync('./build/wasm_wasm.js', `// Generated as part of the build, do not edit

View File

@@ -5,20 +5,8 @@
set -e
rustup toolchain install stable
cd packages
cd packages/wasm-crypto
PACKAGES=( $(ls -1d *) )
RUST_BACKTRACE=full cargo test --release -- --nocapture
for PKG in "${PACKAGES[@]}"; do
if [ -f "$PKG/package.json" ]; then
cd $PKG
echo "*** Testing Rust $PKG"
RUST_BACKTRACE=full cargo test --release -- --nocapture
cd ..
fi
done
cd ..
cd ../..