Remove prebuild dependency

Every CI build task tagged with 'package' now populates and
publishes the relevant npm/platform directory as an artefact.
These are aggregated by a fan-in release task at the end to
create a complete npm workspace zipfile. If the commit is
tagged, a release is created and the npm workspace attached.
This commit is contained in:
Lovell Fuller 2025-06-13 12:41:45 +01:00
parent 5374b036f3
commit cab02463ec
6 changed files with 175 additions and 183 deletions

View File

@ -4,15 +4,12 @@ on:
- pull_request - pull_request
permissions: {} permissions: {}
jobs: jobs:
github-runner: build-native:
permissions: permissions:
contents: write contents: read
name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} ${{ matrix.prebuild && '- prebuild' }} name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}"
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
container: container: ${{ matrix.container }}
image: ${{ matrix.container }}
volumes:
- /:/host
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -23,7 +20,7 @@ jobs:
nodejs_version: "^18.17.0" nodejs_version: "^18.17.0"
nodejs_version_major: 18 nodejs_version_major: 18
platform: linux-x64 platform: linux-x64
prebuild: true package: true
- os: ubuntu-24.04 - os: ubuntu-24.04
container: rockylinux:8 container: rockylinux:8
nodejs_arch: x64 nodejs_arch: x64
@ -40,7 +37,7 @@ jobs:
container: node:18-alpine3.17 container: node:18-alpine3.17
nodejs_version_major: 18 nodejs_version_major: 18
platform: linuxmusl-x64 platform: linuxmusl-x64
prebuild: true package: true
- os: ubuntu-24.04 - os: ubuntu-24.04
container: node:20-alpine3.18 container: node:20-alpine3.18
nodejs_version_major: 20 nodejs_version_major: 20
@ -55,28 +52,19 @@ jobs:
nodejs_version: "^18.17.0" nodejs_version: "^18.17.0"
nodejs_version_major: 18 nodejs_version_major: 18
platform: linux-arm64 platform: linux-arm64
prebuild: true package: true
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
container: arm64v8/rockylinux:8 container: arm64v8/rockylinux:8
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^20.3.0" nodejs_version: "^20.3.0"
nodejs_version_major: 20 nodejs_version_major: 20
platform: linux-arm64 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 - os: macos-13
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^18.17.0" nodejs_version: "^18.17.0"
nodejs_version_major: 18 nodejs_version_major: 18
platform: darwin-x64 platform: darwin-x64
prebuild: true package: true
- os: macos-13 - os: macos-13
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^20.3.0" nodejs_version: "^20.3.0"
@ -92,7 +80,7 @@ jobs:
nodejs_version: "^18.17.0" nodejs_version: "^18.17.0"
nodejs_version_major: 18 nodejs_version_major: 18
platform: darwin-arm64 platform: darwin-arm64
prebuild: true package: true
- os: macos-14 - os: macos-14
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^20.3.0" 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: "18.18.2" # pinned to avoid 18.19.0 and npm 10
nodejs_version_major: 18 nodejs_version_major: 18
platform: win32-ia32 platform: win32-ia32
prebuild: true package: true
- os: windows-2022 - os: windows-2022
nodejs_arch: x86 nodejs_arch: x86
nodejs_version: "^20.3.0" nodejs_version: "^20.3.0"
@ -124,7 +112,7 @@ jobs:
nodejs_version: "^18.17.0" nodejs_version: "^18.17.0"
nodejs_version_major: 18 nodejs_version_major: 18
platform: win32-x64 platform: win32-x64
prebuild: true package: true
- os: windows-2022 - os: windows-2022
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^20.3.0" nodejs_version: "^20.3.0"
@ -140,28 +128,18 @@ jobs:
nodejs_version: "^20.3.0" nodejs_version: "^20.3.0"
nodejs_version_major: 20 nodejs_version_major: 20
platform: win32-arm64 platform: win32-arm64
prebuild: true package: true
- os: windows-11-arm - os: windows-11-arm
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^22.9.0" nodejs_version: "^22.9.0"
nodejs_version_major: 22 nodejs_version_major: 22
platform: win32-arm64 platform: win32-arm64
steps: 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) - name: Dependencies (Rocky Linux glibc)
if: contains(matrix.container, 'rockylinux') if: contains(matrix.container, 'rockylinux')
run: | run: |
dnf install -y gcc-toolset-11-gcc-c++ make git python3.12 fontconfig google-noto-sans-fonts dnf install -y gcc-toolset-14-gcc-c++ make git python3.12 fontconfig google-noto-sans-fonts
echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH echo "/opt/rh/gcc-toolset-14/root/usr/bin" >> $GITHUB_PATH
- name: Dependencies (Linux musl) - name: Dependencies (Linux musl)
if: contains(matrix.container, 'alpine') if: contains(matrix.container, 'alpine')
run: apk add build-base git python3 font-noto --update-cache run: apk add build-base git python3 font-noto --update-cache
@ -176,31 +154,70 @@ jobs:
with: with:
node-version: ${{ matrix.nodejs_version }} node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }} architecture: ${{ matrix.nodejs_arch }}
- name: Checkout - uses: actions/checkout@v4
uses: actions/checkout@v4
- name: Install - name: Install
run: npm install --build-from-source run: npm install --build-from-source
- name: Test - name: Test
run: npm test run: npm test
- name: Test packaging - name: Populate npm package
run: | if: matrix.package
npm run package-from-local-build run: npm run package-from-local-build
npm pkg set "optionalDependencies.@img/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}" - uses: actions/upload-artifact@v4
npm run clean if: matrix.package
npm install --ignore-scripts with:
npm test name: ${{ matrix.platform }}
- name: Prebuild path: npm/${{ matrix.platform }}
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/') retention-days: 1
env: if-no-files-found: error
prebuild_upload: ${{ secrets.GITHUB_TOKEN }} build-linuxmusl-arm-64:
run: |
node -e "require('fs').cpSync('package.json', 'src/package.json')"
cd src
npx prebuild
github-runner-qemu:
permissions: permissions:
contents: write contents: read
name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} - prebuild 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 runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
@ -233,8 +250,6 @@ jobs:
with: with:
arch: ${{ matrix.run_on_arch }} arch: ${{ matrix.run_on_arch }}
distro: ${{ matrix.distro }} distro: ${{ matrix.distro }}
env: |
prebuild_upload: "${{ startsWith(github.ref, 'refs/tags/') && secrets.GITHUB_TOKEN || '' }}"
run: | run: |
apt-get update apt-get update
apt-get install -y curl g++ git libatomic1 make python3 xz-utils apt-get install -y curl g++ git libatomic1 make python3 xz-utils
@ -244,20 +259,20 @@ jobs:
npm install --build-from-source npm install --build-from-source
npx mocha --no-config --spec=test/unit/io.js --timeout=30000 npx mocha --no-config --spec=test/unit/io.js --timeout=30000
npm run package-from-local-build npm run package-from-local-build
npm pkg set "optionalDependencies.@img/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}" - uses: actions/upload-artifact@v4
npm run clean with:
npm install --ignore-scripts name: ${{ matrix.platform }}
npx mocha --no-config --spec=test/unit/io.js --timeout=30000 path: npm/${{ matrix.platform }}
[[ -n $prebuild_upload ]] && cd src && ln -s ../package.json && npx prebuild || true retention-days: 1
github-runner-emscripten: if-no-files-found: error
build-emscripten:
permissions: permissions:
contents: write contents: read
name: wasm32 - prebuild name: "build-wasm32 [package]"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
container: "emscripten/emsdk:4.0.10" container: "emscripten/emsdk:4.0.10"
steps: steps:
- name: Checkout - uses: actions/checkout@v4
uses: actions/checkout@v4
- name: Dependencies - name: Dependencies
run: apt-get update && apt-get install -y pkg-config run: apt-get update && apt-get install -y pkg-config
- name: Dependencies (Node.js) - name: Dependencies (Node.js)
@ -275,17 +290,35 @@ jobs:
test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP" test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP"
- name: Test - name: Test
run: emmake npm test run: emmake npm test
- name: Test packaging - name: Populate npm package
run: | run: emmake npm run package-from-local-build
emmake npm run package-from-local-build - uses: actions/upload-artifact@v4
npm pkg set "optionalDependencies.@img/sharp-wasm32=file:./npm/wasm32" with:
npm run clean name: wasm32
rm -rf node_modules/@img/sharp-linux-x64 path: npm/wasm32
npm install --cpu=wasm32 retention-days: 1
npm test if-no-files-found: error
- name: Prebuild release:
if: startsWith(github.ref, 'refs/tags/') permissions:
env: contents: write
npm_config_nodedir: emscripten runs-on: ubuntu-24.04
prebuild_upload: ${{ secrets.GITHUB_TOKEN }} needs:
run: cd src && ln -s ../package.json && emmake npx prebuild --platform=emscripten --arch=wasm32 --strip=0 - 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') }}

View File

@ -1,6 +0,0 @@
{
"runtime": "napi",
"include-regex": "(sharp-.+\\.node|libvips-.+\\.dll)",
"prerelease": true,
"strip": true
}

View File

@ -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);
}
});

View File

@ -3,24 +3,62 @@
'use strict'; 'use strict';
// Populate contents of a single npm/sharpen-sharp-<build-platform> package // Populate the npm package for the current platform with the local build
// with the local/CI build directory for local/CI prebuild testing
const fs = require('node:fs'); const { copyFileSync, cpSync, readFileSync, writeFileSync, appendFileSync } = require('node:fs');
const path = require('node:path'); const { basename, join } = require('node:path');
const { buildPlatformArch } = require('../lib/libvips'); const { buildPlatformArch } = require('../lib/libvips');
const platform = buildPlatformArch();
const dest = path.join(__dirname, platform);
// Use same config as prebuild to copy binary files const licensing = `
const release = path.join(__dirname, '..', 'src', 'build', 'Release'); ## Licensing
const prebuildrc = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.prebuildrc'), 'utf8'));
const include = new RegExp(prebuildrc['include-regex'], 'i'); Copyright 2013 Lovell Fuller and others.
fs.cpSync(release, path.join(dest, 'lib'), {
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, recursive: true,
filter: (file) => { filter: (file) => {
const name = path.basename(file); const name = basename(file);
return name === 'Release' || include.test(name); 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);
}

View File

@ -92,7 +92,7 @@
"Don Denton <don@happycollision.com>" "Don Denton <don@happycollision.com>"
], ],
"scripts": { "scripts": {
"install": "node install/check", "install": "node install/check.js",
"clean": "rm -rf src/build/ .nyc_output/ coverage/ test/fixtures/output.*", "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": "npm run test-lint && npm run test-unit && npm run test-licensing && npm run test-types",
"test-lint": "semistandard && cpplint", "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-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;LGPL-3.0-or-later;MIT\"",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"test-types": "tsd", "test-types": "tsd",
"package-from-local-build": "node npm/from-local-build", "package-from-local-build": "node npm/from-local-build.js",
"package-from-github-release": "node npm/from-github-release",
"docs-build": "node docs/build.mjs", "docs-build": "node docs/build.mjs",
"docs-serve": "cd docs && npm start", "docs-serve": "cd docs && npm start",
"docs-publish": "cd docs && npm run build && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp" "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-libvips-linuxmusl-x64": "1.2.0-rc.2",
"@img/sharp-linux-arm": "0.34.2", "@img/sharp-linux-arm": "0.34.2",
"@img/sharp-linux-arm64": "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-s390x": "0.34.2",
"@img/sharp-linux-x64": "0.34.2", "@img/sharp-linux-x64": "0.34.2",
"@img/sharp-linuxmusl-arm64": "0.34.2", "@img/sharp-linuxmusl-arm64": "0.34.2",
@ -181,8 +181,8 @@
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^11.6.0", "mocha": "^11.6.0",
"node-addon-api": "^8.3.1", "node-addon-api": "^8.3.1",
"node-gyp": "^11.2.0",
"nyc": "^17.1.0", "nyc": "^17.1.0",
"prebuild": "^13.0.1",
"semistandard": "^17.0.0", "semistandard": "^17.0.0",
"tar-fs": "^3.0.9", "tar-fs": "^3.0.9",
"tsd": "^0.32.0" "tsd": "^0.32.0"
@ -197,11 +197,6 @@
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"binary": {
"napi_versions": [
9
]
},
"semistandard": { "semistandard": {
"env": [ "env": [
"mocha" "mocha"

View File

@ -163,6 +163,8 @@
}, },
'xcode_settings': { 'xcode_settings': {
'OTHER_LDFLAGS': [ 'OTHER_LDFLAGS': [
'-Wl,-s',
'-Wl,-dead_strip',
# Ensure runtime linking is relative to sharp.node # 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)/lib\'',
'-Wl,-rpath,\'@loader_path/../../../sharp-libvips-<(platform_and_arch)/<(sharp_libvips_version)/lib\'', '-Wl,-rpath,\'@loader_path/../../../sharp-libvips-<(platform_and_arch)/<(sharp_libvips_version)/lib\'',
@ -176,6 +178,9 @@
'defines': [ 'defines': [
'_GLIBCXX_USE_CXX11_ABI=1' '_GLIBCXX_USE_CXX11_ABI=1'
], ],
'cflags_cc': [
'<!(node -p "require(\'detect-libc\').isNonGlibcLinuxSync() ? \'\' : \'-flto=auto\'")'
],
'link_settings': { 'link_settings': {
'libraries': [ 'libraries': [
'-l:libvips-cpp.so.<(vips_version)' '-l:libvips-cpp.so.<(vips_version)'