diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d67993b2..ec95b072 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,15 +4,12 @@ on: - pull_request permissions: {} jobs: - github-runner: + build-native: permissions: - contents: write - name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} ${{ matrix.prebuild && '- prebuild' }} + contents: read + name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}" runs-on: ${{ matrix.os }} - container: - image: ${{ matrix.container }} - volumes: - - /:/host + container: ${{ matrix.container }} strategy: fail-fast: false matrix: @@ -23,7 +20,7 @@ jobs: nodejs_version: "^18.17.0" nodejs_version_major: 18 platform: linux-x64 - prebuild: true + package: true - os: ubuntu-24.04 container: rockylinux:8 nodejs_arch: x64 @@ -40,7 +37,7 @@ jobs: container: node:18-alpine3.17 nodejs_version_major: 18 platform: linuxmusl-x64 - prebuild: true + package: true - os: ubuntu-24.04 container: node:20-alpine3.18 nodejs_version_major: 20 @@ -55,28 +52,19 @@ jobs: nodejs_version: "^18.17.0" nodejs_version_major: 18 platform: linux-arm64 - prebuild: true + package: true - os: ubuntu-24.04-arm container: arm64v8/rockylinux:8 nodejs_arch: arm64 nodejs_version: "^20.3.0" nodejs_version_major: 20 platform: linux-arm64 - - os: ubuntu-24.04-arm - container: node:18-alpine3.17 - nodejs_version_major: 18 - platform: linuxmusl-arm64 - prebuild: true - - os: ubuntu-24.04-arm - container: node:20-alpine3.18 - nodejs_version_major: 20 - platform: linuxmusl-arm64 - os: macos-13 nodejs_arch: x64 nodejs_version: "^18.17.0" nodejs_version_major: 18 platform: darwin-x64 - prebuild: true + package: true - os: macos-13 nodejs_arch: x64 nodejs_version: "^20.3.0" @@ -92,7 +80,7 @@ jobs: nodejs_version: "^18.17.0" nodejs_version_major: 18 platform: darwin-arm64 - prebuild: true + package: true - os: macos-14 nodejs_arch: arm64 nodejs_version: "^20.3.0" @@ -108,7 +96,7 @@ jobs: nodejs_version: "18.18.2" # pinned to avoid 18.19.0 and npm 10 nodejs_version_major: 18 platform: win32-ia32 - prebuild: true + package: true - os: windows-2022 nodejs_arch: x86 nodejs_version: "^20.3.0" @@ -124,7 +112,7 @@ jobs: nodejs_version: "^18.17.0" nodejs_version_major: 18 platform: win32-x64 - prebuild: true + package: true - os: windows-2022 nodejs_arch: x64 nodejs_version: "^20.3.0" @@ -140,28 +128,18 @@ jobs: nodejs_version: "^20.3.0" nodejs_version_major: 20 platform: win32-arm64 - prebuild: true + package: true - os: windows-11-arm nodejs_arch: arm64 nodejs_version: "^22.9.0" nodejs_version_major: 22 platform: win32-arm64 steps: - - name: Allow Linux musl containers on ARM64 runners # https://github.com/actions/runner/issues/801#issuecomment-2394425757 - if: matrix.platform == 'linuxmusl-arm64' - shell: sh - run: | - apk add nodejs - sed -i "s:ID=alpine:ID=NotpineForGHA:" /etc/os-release - cd /host/home/runner/runners/*/externals/ - rm -rf node20/* - mkdir node20/bin - ln -s /usr/bin/node node20/bin/node - name: Dependencies (Rocky Linux glibc) if: contains(matrix.container, 'rockylinux') run: | - dnf install -y gcc-toolset-11-gcc-c++ make git python3.12 fontconfig google-noto-sans-fonts - echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH + dnf install -y gcc-toolset-14-gcc-c++ make git python3.12 fontconfig google-noto-sans-fonts + echo "/opt/rh/gcc-toolset-14/root/usr/bin" >> $GITHUB_PATH - name: Dependencies (Linux musl) if: contains(matrix.container, 'alpine') run: apk add build-base git python3 font-noto --update-cache @@ -176,31 +154,70 @@ jobs: with: node-version: ${{ matrix.nodejs_version }} architecture: ${{ matrix.nodejs_arch }} - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - name: Install run: npm install --build-from-source - name: Test run: npm test - - name: Test packaging - run: | - npm run package-from-local-build - npm pkg set "optionalDependencies.@img/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}" - npm run clean - npm install --ignore-scripts - npm test - - name: Prebuild - if: matrix.prebuild && startsWith(github.ref, 'refs/tags/') - env: - prebuild_upload: ${{ secrets.GITHUB_TOKEN }} - run: | - node -e "require('fs').cpSync('package.json', 'src/package.json')" - cd src - npx prebuild - github-runner-qemu: + - name: Populate npm package + if: matrix.package + run: npm run package-from-local-build + - uses: actions/upload-artifact@v4 + if: matrix.package + with: + name: ${{ matrix.platform }} + path: npm/${{ matrix.platform }} + retention-days: 1 + if-no-files-found: error + build-linuxmusl-arm-64: permissions: - contents: write - name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} - prebuild + contents: read + name: "build-linuxmusl-arm64 [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}" + runs-on: ubuntu-24.04-arm + container: + image: ${{ matrix.container }} + volumes: + - /:/host + strategy: + fail-fast: false + matrix: + include: + - container: node:18-alpine3.17 + nodejs_version_major: 18 + package: true + - container: node:20-alpine3.18 + nodejs_version_major: 20 + steps: + - name: Allow Linux musl containers on ARM64 runners # https://github.com/actions/runner/issues/801#issuecomment-2394425757 + shell: sh + run: | + apk add nodejs + sed -i "s:ID=alpine:ID=NotpineForGHA:" /etc/os-release + cd /host/home/runner/runners/*/externals/ + rm -rf node20/* + mkdir node20/bin + ln -s /usr/bin/node node20/bin/node + - name: Dependencies + run: apk add build-base git python3 font-noto --update-cache + - uses: actions/checkout@v4 + - name: Install + run: npm install --build-from-source + - name: Test + run: npm test + - name: Populate npm package + if: matrix.package + run: npm run package-from-local-build + - uses: actions/upload-artifact@v4 + if: matrix.package + with: + name: linuxmusl-arm64 + path: npm/linuxmusl-arm64 + retention-days: 1 + if-no-files-found: error + build-qemu: + permissions: + contents: read + name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] [package]" runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -233,8 +250,6 @@ jobs: with: arch: ${{ matrix.run_on_arch }} distro: ${{ matrix.distro }} - env: | - prebuild_upload: "${{ startsWith(github.ref, 'refs/tags/') && secrets.GITHUB_TOKEN || '' }}" run: | apt-get update apt-get install -y curl g++ git libatomic1 make python3 xz-utils @@ -244,20 +259,20 @@ jobs: npm install --build-from-source npx mocha --no-config --spec=test/unit/io.js --timeout=30000 npm run package-from-local-build - npm pkg set "optionalDependencies.@img/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}" - npm run clean - npm install --ignore-scripts - npx mocha --no-config --spec=test/unit/io.js --timeout=30000 - [[ -n $prebuild_upload ]] && cd src && ln -s ../package.json && npx prebuild || true - github-runner-emscripten: + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.platform }} + path: npm/${{ matrix.platform }} + retention-days: 1 + if-no-files-found: error + build-emscripten: permissions: - contents: write - name: wasm32 - prebuild + contents: read + name: "build-wasm32 [package]" runs-on: ubuntu-24.04 container: "emscripten/emsdk:4.0.10" steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - name: Dependencies run: apt-get update && apt-get install -y pkg-config - name: Dependencies (Node.js) @@ -275,17 +290,35 @@ jobs: test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP" - name: Test run: emmake npm test - - name: Test packaging - run: | - emmake npm run package-from-local-build - npm pkg set "optionalDependencies.@img/sharp-wasm32=file:./npm/wasm32" - npm run clean - rm -rf node_modules/@img/sharp-linux-x64 - npm install --cpu=wasm32 - npm test - - name: Prebuild - if: startsWith(github.ref, 'refs/tags/') - env: - npm_config_nodedir: emscripten - prebuild_upload: ${{ secrets.GITHUB_TOKEN }} - run: cd src && ln -s ../package.json && emmake npx prebuild --platform=emscripten --arch=wasm32 --strip=0 + - name: Populate npm package + run: emmake npm run package-from-local-build + - uses: actions/upload-artifact@v4 + with: + name: wasm32 + path: npm/wasm32 + retention-days: 1 + if-no-files-found: error + release: + permissions: + contents: write + runs-on: ubuntu-24.04 + needs: + - build-native + - build-linuxmusl-arm-64 + - build-qemu + - build-emscripten + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + path: npm + - name: Create npm workspace tarball + run: tar -vcaf npm-workspace.tar.xz --directory npm --exclude=from-local-build.js . + - name: Create GitHub release for tag + if: startsWith(github.ref, 'refs/tags/v') + uses: ncipollo/release-action@v1 + with: + artifacts: npm-workspace.tar.xz + artifactContentType: application/x-xz + prerelease: ${{ contains(github.ref, '-rc') }} + makeLatest: ${{ !contains(github.ref, '-rc') }} diff --git a/.prebuildrc b/.prebuildrc deleted file mode 100644 index 0a4ccd2e..00000000 --- a/.prebuildrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "runtime": "napi", - "include-regex": "(sharp-.+\\.node|libvips-.+\\.dll)", - "prerelease": true, - "strip": true -} diff --git a/npm/from-github-release.js b/npm/from-github-release.js deleted file mode 100644 index 84f9fb9b..00000000 --- a/npm/from-github-release.js +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; - -// Populate contents of all packages with the current GitHub release - -const { readFile, writeFile, appendFile, copyFile, rm } = require('node:fs/promises'); -const path = require('node:path'); -const { Readable } = require('node:stream'); -const { pipeline } = require('node:stream/promises'); -const { createGunzip } = require('node:zlib'); -const { extract } = require('tar-fs'); - -const { workspaces } = require('./package.json'); -const { version } = require('../package.json'); - -const mapTarballEntry = (header) => { - header.name = path.basename(header.name); - return header; -}; - -const licensing = ` -## Licensing - -Copyright 2013 Lovell Fuller and others. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at -[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -`; - -workspaces.map(async platform => { - const prebuildPlatform = platform === 'wasm32' ? 'emscripten-wasm32' : platform; - const url = `https://github.com/lovell/sharp/releases/download/v${version}/sharp-v${version}-napi-v9-${prebuildPlatform}.tar.gz`; - const dir = path.join(__dirname, platform); - const response = await fetch(url); - if (!response.ok) { - console.log(`Skipping ${platform}: ${response.statusText}`); - return; - } - // Extract prebuild tarball - const lib = path.join(dir, 'lib'); - await rm(lib, { force: true, recursive: true }); - await pipeline( - Readable.fromWeb(response.body), - createGunzip(), - extract(lib, { map: mapTarballEntry }) - ); - // Generate README - const { name, description } = require(`./${platform}/package.json`); - await writeFile(path.join(dir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`); - // Copy Apache-2.0 LICENSE - await copyFile(path.join(__dirname, '..', 'LICENSE'), path.join(dir, 'LICENSE')); - // Copy files for packages without an explicit sharp-libvips dependency (Windows, wasm) - if (platform.startsWith('win') || platform.startsWith('wasm')) { - const libvipsPlatform = platform === 'wasm32' ? 'dev-wasm32' : platform; - const sharpLibvipsDir = path.join(require(`@img/sharp-libvips-${libvipsPlatform}/lib`), '..'); - // Copy versions.json - await copyFile(path.join(sharpLibvipsDir, 'versions.json'), path.join(dir, 'versions.json')); - // Append third party licensing to README - const readme = await readFile(path.join(sharpLibvipsDir, 'README.md'), { encoding: 'utf-8' }); - const thirdParty = readme.substring(readme.indexOf('\nThis software contains')); - appendFile(path.join(dir, 'README.md'), thirdParty); - } -}); diff --git a/npm/from-local-build.js b/npm/from-local-build.js index ad7593ec..2fee2db9 100644 --- a/npm/from-local-build.js +++ b/npm/from-local-build.js @@ -3,24 +3,62 @@ 'use strict'; -// Populate contents of a single npm/sharpen-sharp- package -// with the local/CI build directory for local/CI prebuild testing +// Populate the npm package for the current platform with the local build -const fs = require('node:fs'); -const path = require('node:path'); +const { copyFileSync, cpSync, readFileSync, writeFileSync, appendFileSync } = require('node:fs'); +const { basename, join } = require('node:path'); const { buildPlatformArch } = require('../lib/libvips'); -const platform = buildPlatformArch(); -const dest = path.join(__dirname, platform); -// Use same config as prebuild to copy binary files -const release = path.join(__dirname, '..', 'src', 'build', 'Release'); -const prebuildrc = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.prebuildrc'), 'utf8')); -const include = new RegExp(prebuildrc['include-regex'], 'i'); -fs.cpSync(release, path.join(dest, 'lib'), { +const licensing = ` +## Licensing + +Copyright 2013 Lovell Fuller and others. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +`; + +const platform = buildPlatformArch(); +const destDir = join(__dirname, platform); +console.log(`Populating npm package for platform: ${platform}`); + +// Copy binaries +const releaseDir = join(__dirname, '..', 'src', 'build', 'Release'); +const libDir = join(destDir, 'lib'); +cpSync(releaseDir, libDir, { recursive: true, filter: (file) => { - const name = path.basename(file); - return name === 'Release' || include.test(name); + const name = basename(file); + return name === 'Release' || + (name.startsWith('sharp-') && name.includes('.node')) || + (name.startsWith('libvips-') && name.endsWith('.dll')); } }); + +// Generate README +const { name, description } = require(`./${platform}/package.json`); +writeFileSync(join(destDir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`); + +// Copy Apache-2.0 LICENSE +copyFileSync(join(__dirname, '..', 'LICENSE'), join(destDir, 'LICENSE')); + +// Copy files for packages without an explicit sharp-libvips dependency (Windows, wasm) +if (platform.startsWith('win') || platform.startsWith('wasm')) { + const libvipsPlatform = platform === 'wasm32' ? 'dev-wasm32' : platform; + const sharpLibvipsDir = join(require(`@img/sharp-libvips-${libvipsPlatform}/lib`), '..'); + // Copy versions.json + copyFileSync(join(sharpLibvipsDir, 'versions.json'), join(destDir, 'versions.json')); + // Append third party licensing to README + const readme = readFileSync(join(sharpLibvipsDir, 'README.md'), { encoding: 'utf-8' }); + const thirdParty = readme.substring(readme.indexOf('\nThis software contains')); + appendFileSync(join(destDir, 'README.md'), thirdParty); +} diff --git a/package.json b/package.json index 93100990..0536050b 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "Don Denton " ], "scripts": { - "install": "node install/check", + "install": "node install/check.js", "clean": "rm -rf src/build/ .nyc_output/ coverage/ test/fixtures/output.*", "test": "npm run test-lint && npm run test-unit && npm run test-licensing && npm run test-types", "test-lint": "semistandard && cpplint", @@ -100,8 +100,7 @@ "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;LGPL-3.0-or-later;MIT\"", "test-leak": "./test/leak/leak.sh", "test-types": "tsd", - "package-from-local-build": "node npm/from-local-build", - "package-from-github-release": "node npm/from-github-release", + "package-from-local-build": "node npm/from-local-build.js", "docs-build": "node docs/build.mjs", "docs-serve": "cd docs && npm start", "docs-publish": "cd docs && npm run build && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp" @@ -155,6 +154,7 @@ "@img/sharp-libvips-linuxmusl-x64": "1.2.0-rc.2", "@img/sharp-linux-arm": "0.34.2", "@img/sharp-linux-arm64": "0.34.2", + "@img/sharp-linux-ppc64": "0.34.2", "@img/sharp-linux-s390x": "0.34.2", "@img/sharp-linux-x64": "0.34.2", "@img/sharp-linuxmusl-arm64": "0.34.2", @@ -181,8 +181,8 @@ "license-checker": "^25.0.1", "mocha": "^11.6.0", "node-addon-api": "^8.3.1", + "node-gyp": "^11.2.0", "nyc": "^17.1.0", - "prebuild": "^13.0.1", "semistandard": "^17.0.0", "tar-fs": "^3.0.9", "tsd": "^0.32.0" @@ -197,11 +197,6 @@ "funding": { "url": "https://opencollective.com/libvips" }, - "binary": { - "napi_versions": [ - 9 - ] - }, "semistandard": { "env": [ "mocha" diff --git a/src/binding.gyp b/src/binding.gyp index 0fbf515e..ae38a29f 100644 --- a/src/binding.gyp +++ b/src/binding.gyp @@ -163,6 +163,8 @@ }, 'xcode_settings': { 'OTHER_LDFLAGS': [ + '-Wl,-s', + '-Wl,-dead_strip', # Ensure runtime linking is relative to sharp.node '-Wl,-rpath,\'@loader_path/../../sharp-libvips-<(platform_and_arch)/lib\'', '-Wl,-rpath,\'@loader_path/../../../sharp-libvips-<(platform_and_arch)/<(sharp_libvips_version)/lib\'', @@ -176,6 +178,9 @@ 'defines': [ '_GLIBCXX_USE_CXX11_ABI=1' ], + 'cflags_cc': [ + '