diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index 9c9b8ca..7f8df2a 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - dependency: ['swipl', 'emsdk', 'zlib', 'gmp'] + dependency: ['swipl', 'emsdk', 'zlib', 'pcre2'] steps: - uses: actions/checkout@v3 diff --git a/README.md b/README.md index 6c8a716..13521ef 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,23 @@ The package can be built using npm or yarn. Please use yarn to add new dependencies and update yarn.lock file. SWI-Prolog WebAssembly version is currently built inside Docker with Emscripten. +### Development + +To develop with this package, clone the repository and run: + +``` +# Install dependencies +npm ci + +# Build the Webassembly +npm run build + +# Run tests +npm t +``` + +*Note* You need Docker and Node 16 or higher to installed build the package. + ## Versioning The package uses its own versioning scheme using semver. It is diff --git a/docker/Dockerfile b/docker/Dockerfile index 60d48fa..4e6541e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,36 +6,48 @@ FROM emscripten/emsdk:$EMSDK_VERSION # Installs build dependencies. RUN apt-get update && apt-get install -y bzip2 lzip ninja-build xz-utils -# Downloads and builds ZLIB. +RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - +RUN apt install -y nodejs +RUN sed -i 's/NODE_JS .*/NODE_JS = "\/usr\/bin\/node"/' /emsdk/.emscripten + +# Download dependency sources +WORKDIR /wasm ARG ZLIB_VERSION -RUN wget --no-verbose -qO- https://zlib.net/zlib-$ZLIB_VERSION.tar.gz | tar xvz --directory / +ARG PCRE2_NAME +RUN wget --no-verbose -qO- https://zlib.net/zlib-$ZLIB_VERSION.tar.gz | tar xvz +RUN git clone --branch=$PCRE2_NAME --depth 1 https://github.com/PCRE2Project/pcre2 -WORKDIR /zlib-$ZLIB_VERSION -RUN emconfigure ./configure +# Build the dependencies and install them in /wasm +WORKDIR /wasm/zlib-$ZLIB_VERSION +RUN emconfigure ./configure --static --prefix=/wasm RUN EMCC_CFLAGS=-Wno-deprecated-non-prototype emmake make +RUN emmake make install -# Downloads and builds GMP. -ARG GMP_VERSION -RUN wget --no-verbose -qO- https://gmplib.org/download/gmp/gmp-$GMP_VERSION.tar.lz | tar x --lzip --directory / - -WORKDIR /gmp-$GMP_VERSION -RUN mkdir /gmp -RUN emconfigure ./configure --host=none --disable-assembly --prefix=/gmp -RUN make -RUN make install +WORKDIR /wasm/pcre2/build +RUN emcmake cmake -DCMAKE_INSTALL_PREFIX=/wasm \ + -DPCRE2GREP_SUPPORT_JIT=OFF \ + -G Ninja .. $@ +RUN ninja && ninja install -# Clones SWI-Prolog. -ARG SWIPL_VERSION -RUN git clone --depth 1 --branch V$SWIPL_VERSION --recurse-submodules https://github.com/SWI-Prolog/swipl-devel /swipl-devel -j100 +# Clone SWI-Prolog. Only make a shallow clone and only clone the +# submodules we need at depth 1. +WORKDIR / +RUN git clone --depth 1 https://github.com/SWI-Prolog/swipl-devel +RUN cd swipl-devel && git submodule update --init --depth 1 \ + packages/chr packages/clib packages/clpqr packages/http packages/nlp \ + packages/pcre packages/plunit packages/sgml packages/RDF \ + packages/semweb packages/zlib # Build SWIPL WORKDIR /swipl-devel/build.wasm RUN mkdir -p /swipl-devel/build.wasm RUN cmake -DCMAKE_TOOLCHAIN_FILE=/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \ -DCMAKE_BUILD_TYPE=Release \ - -DZLIB_LIBRARY=/zlib-$ZLIB_VERSION/libz.a \ - -DZLIB_INCLUDE_DIR=/zlib-$ZLIB_VERSION \ - -DGMP_ROOT=/gmp \ + -DCMAKE_FIND_ROOT_PATH=/wasm \ + -DUSE_GMP=OFF \ -DINSTALL_DOCUMENTATION=OFF \ + -DNODE_JS_EXECUTABLE=/usr/bin/node \ -G Ninja .. RUN ninja + +RUN ctest -j $(nproc) --output-on-failure diff --git a/examples/generation/package-lock.json b/examples/generation/package-lock.json index 21f0ddb..dd607c0 100644 --- a/examples/generation/package-lock.json +++ b/examples/generation/package-lock.json @@ -15,6 +15,39 @@ "ts-node": "^10.9.1" } }, + "../..": { + "name": "swipl-wasm", + "version": "3.1.0", + "license": "BSD-2-Clause", + "bin": { + "swipl-generate": "dist/bin/index.js" + }, + "devDependencies": { + "@octokit/rest": "^19.0.7", + "@qiwi/semantic-release-gh-pages-plugin": "^5.2.5", + "@types/emscripten": "^1.39.6", + "@types/fs-extra": "^11.0.1", + "@types/node": "^20.0.0", + "@typescript-eslint/eslint-plugin": "^5.59.2", + "@typescript-eslint/parser": "^5.59.2", + "cross-fetch": "^3.1.5", + "eslint": "^8.39.0", + "fs-extra": "^11.1.1", + "http-server": "^14.1.1", + "mocha": "^10.2.0", + "node-static": "^0.7.11", + "npm-run-all": "^4.1.5", + "puppeteer": "^20.1.1", + "semantic-release": "^19.0.5", + "ts-node": "^10.9.1", + "typescript": "^5.0.4", + "webpack": "^5.82.0", + "webpack-cli": "^5.0.2" + }, + "peerDependencies": { + "@types/emscripten": "^1.39.6" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -76,12 +109,6 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, - "node_modules/@types/emscripten": { - "version": "1.39.6", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.6.tgz", - "integrity": "sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg==", - "peer": true - }, "node_modules/@types/node": { "version": "18.14.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.0.tgz", @@ -138,15 +165,8 @@ "dev": true }, "node_modules/swipl-wasm": { - "version": "3.1.0", - "resolved": "file:../..", - "license": "BSD-2-Clause", - "bin": { - "swipl-generate": "dist/bin/index.js" - }, - "peerDependencies": { - "@types/emscripten": "^1.39.6" - } + "resolved": "../..", + "link": true }, "node_modules/ts-node": { "version": "10.9.1", @@ -277,12 +297,6 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, - "@types/emscripten": { - "version": "1.39.6", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.6.tgz", - "integrity": "sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg==", - "peer": true - }, "@types/node": { "version": "18.14.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.0.tgz", @@ -327,8 +341,29 @@ "dev": true }, "swipl-wasm": { - "version": "3.1.0", - "requires": {} + "version": "file:../..", + "requires": { + "@octokit/rest": "^19.0.7", + "@qiwi/semantic-release-gh-pages-plugin": "^5.2.5", + "@types/emscripten": "^1.39.6", + "@types/fs-extra": "^11.0.1", + "@types/node": "^20.0.0", + "@typescript-eslint/eslint-plugin": "^5.59.2", + "@typescript-eslint/parser": "^5.59.2", + "cross-fetch": "^3.1.5", + "eslint": "^8.39.0", + "fs-extra": "^11.1.1", + "http-server": "^14.1.1", + "mocha": "^10.2.0", + "node-static": "^0.7.11", + "npm-run-all": "^4.1.5", + "puppeteer": "^20.1.1", + "semantic-release": "^19.0.5", + "ts-node": "^10.9.1", + "typescript": "^5.0.4", + "webpack": "^5.82.0", + "webpack-cli": "^5.0.2" + } }, "ts-node": { "version": "10.9.1", diff --git a/package.json b/package.json index 41497e0..ad9084a 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "dist/**/*.wasm" ], "scripts": { - "build:wasm-docker:build": "docker build --build-arg SWIPL_VERSION=$npm_package_config_swipl_version --build-arg EMSDK_VERSION=$npm_package_config_emsdk_version --build-arg ZLIB_VERSION=$npm_package_config_zlib_version --build-arg GMP_VERSION=$npm_package_config_gmp_version -t swipl-wasm-image docker", + "build:wasm-docker:build": "docker build --build-arg SWIPL_VERSION=$npm_package_config_swipl_version --build-arg EMSDK_VERSION=$npm_package_config_emsdk_version --build-arg ZLIB_VERSION=$npm_package_config_zlib_version --build-arg PCRE2_NAME=$npm_package_config_pcre2_name -t swipl-wasm-image docker", "build:wasm-docker:create": "docker create --name swipl-wasm swipl-wasm-image", "build:wasm-docker:remove": "docker rm swipl-wasm", "build:wasm-docker:extract:data": "docker cp swipl-wasm:/swipl-devel/build.wasm/src/swipl-web.data dist/swipl/swipl-web.data", @@ -68,7 +68,7 @@ "lint:tests": "eslint tests --ext .js", "lint": "run-s lint:types lint:tests", "update:dep:emsdk": "ts-node scripts/get-latest-emsdk", - "update:dep:gmp": "ts-node scripts/get-latest-gmp", + "update:dep:pcre2": "ts-node scripts/get-latest-pcre2", "update:dep:swipl": "ts-node scripts/get-latest-swipl", "update:dep:zlib": "ts-node scripts/get-latest-zlib", "update:dep": "run-s update:dep:*", @@ -84,13 +84,17 @@ "commit": "20b7ede92a13cd37430673bae1c0fcbc9cfcaf9d" }, "emsdk": { - "version": "3.1.34" + "version": "3.1.38", + "commit": "0329dbaa2593dcb6604caf38893f276b06cc04ef", + "name": "3.1.38" }, "zlib": { "version": "1.2.13" }, - "gmp": { - "version": "6.2.1" + "pcre2": { + "version": "10.42.0", + "commit": "52c08847921a324c804cabf2814549f50bce1265", + "name": "pcre2-10.42" } }, "bin": { diff --git a/scripts/get-latest-gmp.ts b/scripts/get-latest-gmp.ts deleted file mode 100644 index 6f73173..0000000 --- a/scripts/get-latest-gmp.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { getPackage, isHigherVersion, savePackage } from './util'; -import { fetch } from 'cross-fetch'; - -async function main() { - const downloads = await fetch('https://gmplib.org/download/gmp/'); - const text = await downloads.text(); - const versions = text.match(/\"gmp-\d+.\d+.\d+.tar.xz\"/g)!.map((elem: string) => elem.slice(5, -8)); - - let bestVersion: string | undefined; - - for (const version of versions) { - if (!bestVersion || isHigherVersion(version, bestVersion)) - bestVersion = version; - } - - const pkg = getPackage(); - - // If a higher version exists, update the package.json - if (bestVersion && isHigherVersion(bestVersion, pkg.config.gmp.version)) { - pkg.config.gmp.version = bestVersion; - savePackage(pkg); - } -} - -main(); diff --git a/scripts/get-latest-pcre2.ts b/scripts/get-latest-pcre2.ts new file mode 100644 index 0000000..8c023aa --- /dev/null +++ b/scripts/get-latest-pcre2.ts @@ -0,0 +1,9 @@ +import { updateTag } from './util'; + +updateTag({ + owner: 'PCRE2Project', + repo: 'pcre2', + // Since pcre2 only does major.minor we add .0 to make it the semver we like + getVersion: (tag) => /^pcre2\-\d+.\d+$/.test(tag.name) ? tag.name.slice(6) + '.0' : undefined, + entry: 'pcre2', +}); diff --git a/scripts/util.ts b/scripts/util.ts index 99da8c4..b42b818 100644 --- a/scripts/util.ts +++ b/scripts/util.ts @@ -49,11 +49,11 @@ export interface IUpdateTagOptions { export async function updateTag(options: IUpdateTagOptions) { const data = await getAllTags(options); - let bestElem: { version: string; commit: string } | undefined; + let bestElem: { version: string; commit: string; name?: string } | undefined; for (const elem of data) { const version = options.getVersion(elem); if (version && (!bestElem || isHigherVersion(version, bestElem.version))) { - bestElem = { version, commit: elem.commit.sha } + bestElem = { version, commit: elem.commit.sha, name: elem.name }; } } diff --git a/tests/node.js b/tests/node.js index 88d1681..13076f9 100644 --- a/tests/node.js +++ b/tests/node.js @@ -77,5 +77,11 @@ describe("SWI-Prolog WebAssembly on Node.js", () => { const atom = swipl.prolog.query("X = atom").once().X; assert.strictEqual(atom, "atom"); }); + + it(`[${name}] ` + "should do regex operations enabled by pcre2", async () => { + const swipl = await SWIPL({ arguments: ["-q"], ...addedParams }); + assert.strictEqual(swipl.prolog.query("use_module(library(pcre)).").once().success, true); + assert.strictEqual(swipl.prolog.query("re_match(\"^needle\"/i, \"Needle in a haystack\").").once().success, true); + }); } });