mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
255 lines
7.6 KiB
JavaScript
255 lines
7.6 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const { spawnSync } = require('child_process');
|
|
const {
|
|
copyRuntimeToNativeBuild,
|
|
findLibMpv,
|
|
patchAddonForBundledRuntime,
|
|
validateNoForbiddenRuntimeLinks,
|
|
} = require('../../tools/packaging/embedded-mpv-macos.cjs');
|
|
|
|
const workspaceRoot = process.cwd();
|
|
const addonRoot = path.join(workspaceRoot, 'apps', 'electron-backend', 'native');
|
|
const outputDir = path.join(addonRoot, 'build', 'Release');
|
|
const outputFile = path.join(outputDir, 'embedded_mpv.node');
|
|
const outputLibDir = path.join(outputDir, 'lib');
|
|
const distNativeDir = path.join(
|
|
workspaceRoot,
|
|
'dist',
|
|
'apps',
|
|
'electron-backend',
|
|
'native'
|
|
);
|
|
const unavailableMarkerFile = path.join(outputDir, 'embedded-mpv-unavailable.txt');
|
|
const homebrewIncludeDir = '/opt/homebrew/include';
|
|
const homebrewLibDir = '/opt/homebrew/lib';
|
|
const targetArch =
|
|
process.env.IPTVNATOR_EMBEDDED_MPV_ARCH ||
|
|
process.env.npm_config_arch ||
|
|
process.arch;
|
|
const vendoredRuntimeRoot = path.join(
|
|
workspaceRoot,
|
|
'vendor',
|
|
'embedded-mpv',
|
|
`darwin-${targetArch}`
|
|
);
|
|
const vendoredIncludeDir = path.join(vendoredRuntimeRoot, 'include');
|
|
const vendoredLibDir = path.join(vendoredRuntimeRoot, 'lib');
|
|
const homebrewFallbackEnabled =
|
|
process.env.IPTVNATOR_EMBEDDED_MPV_ALLOW_HOMEBREW === '1';
|
|
const embeddedMpvRequired = ['1', 'true', 'yes', 'on'].includes(
|
|
(process.env.IPTVNATOR_REQUIRE_EMBEDDED_MPV ?? '').trim().toLowerCase()
|
|
);
|
|
|
|
function log(message) {
|
|
process.stdout.write(`[embedded-mpv] ${message}\n`);
|
|
}
|
|
|
|
function cleanOutput() {
|
|
fs.rmSync(outputFile, { force: true });
|
|
fs.rmSync(outputLibDir, { recursive: true, force: true });
|
|
fs.rmSync(path.join(outputDir, '.deps'), { recursive: true, force: true });
|
|
fs.rmSync(path.join(outputDir, 'obj.target'), {
|
|
recursive: true,
|
|
force: true,
|
|
});
|
|
fs.rmSync(path.join(outputDir, 'embedded-mpv-runtime.json'), {
|
|
force: true,
|
|
});
|
|
fs.writeFileSync(
|
|
unavailableMarkerFile,
|
|
'Embedded MPV native runtime is not available for this build.\n'
|
|
);
|
|
}
|
|
|
|
function cleanDistNativeOutput() {
|
|
fs.rmSync(distNativeDir, { recursive: true, force: true });
|
|
}
|
|
|
|
function readRuntimeManifest(runtimeRoot) {
|
|
const manifestPath = path.join(runtimeRoot, 'runtime-manifest.json');
|
|
if (!fs.existsSync(manifestPath)) {
|
|
return {};
|
|
}
|
|
|
|
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
}
|
|
|
|
function resolveRuntime() {
|
|
const vendoredLibMpv = findLibMpv(vendoredLibDir);
|
|
const vendoredHeader = path.join(vendoredIncludeDir, 'mpv', 'client.h');
|
|
|
|
if (vendoredLibMpv && fs.existsSync(vendoredHeader)) {
|
|
return {
|
|
origin: 'vendored-lgpl',
|
|
includeDir: vendoredIncludeDir,
|
|
libDir: vendoredLibDir,
|
|
manifest: readRuntimeManifest(vendoredRuntimeRoot),
|
|
};
|
|
}
|
|
|
|
if (homebrewFallbackEnabled) {
|
|
const homebrewLibMpv = findLibMpv(homebrewLibDir);
|
|
const homebrewHeader = path.join(homebrewIncludeDir, 'mpv', 'client.h');
|
|
if (homebrewLibMpv && fs.existsSync(homebrewHeader)) {
|
|
log(
|
|
'Using Homebrew libmpv as a development-only fallback. Release packaging will reject this runtime.'
|
|
);
|
|
return {
|
|
origin: 'homebrew-dev',
|
|
includeDir: homebrewIncludeDir,
|
|
libDir: homebrewLibDir,
|
|
manifest: {
|
|
warning:
|
|
'Development-only runtime. Do not ship this in release artifacts.',
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function resolveElectronNodeGypBin() {
|
|
const pnpmRoot = path.join(workspaceRoot, 'node_modules', '.pnpm');
|
|
|
|
if (!fs.existsSync(pnpmRoot)) {
|
|
throw new Error('Unable to find node_modules/.pnpm.');
|
|
}
|
|
|
|
const packageDirs = fs
|
|
.readdirSync(pnpmRoot, { withFileTypes: true })
|
|
.filter(
|
|
(entry) =>
|
|
entry.isDirectory() &&
|
|
entry.name.startsWith('@electron+node-gyp@')
|
|
)
|
|
.map((entry) => entry.name)
|
|
.sort();
|
|
|
|
for (const packageDir of packageDirs) {
|
|
const candidate = path.join(
|
|
pnpmRoot,
|
|
packageDir,
|
|
'node_modules',
|
|
'@electron',
|
|
'node-gyp',
|
|
'bin',
|
|
'node-gyp.js'
|
|
);
|
|
|
|
if (fs.existsSync(candidate)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
throw new Error('Unable to resolve @electron/node-gyp.');
|
|
}
|
|
|
|
function runNodeGyp(command, env) {
|
|
const nodeGypBin = resolveElectronNodeGypBin();
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[nodeGypBin, command, '--directory', addonRoot],
|
|
{
|
|
cwd: workspaceRoot,
|
|
env,
|
|
stdio: 'inherit',
|
|
}
|
|
);
|
|
|
|
if (result.status !== 0) {
|
|
throw new Error(`node-gyp ${command} failed with status ${result.status ?? 1}.`);
|
|
}
|
|
}
|
|
|
|
function main() {
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
cleanDistNativeOutput();
|
|
|
|
if (process.platform !== 'darwin') {
|
|
cleanOutput();
|
|
if (embeddedMpvRequired) {
|
|
throw new Error('Embedded MPV is required but this host is not macOS.');
|
|
}
|
|
log('Skipping build on non-macOS host.');
|
|
return;
|
|
}
|
|
|
|
const runtime = resolveRuntime();
|
|
if (!runtime) {
|
|
cleanOutput();
|
|
const message = [
|
|
`Skipping build because no embedded MPV runtime was found for darwin-${targetArch}.`,
|
|
`Expected vendored runtime at ${vendoredRuntimeRoot}.`,
|
|
'For local development only, set IPTVNATOR_EMBEDDED_MPV_ALLOW_HOMEBREW=1 to use Homebrew libmpv.',
|
|
].join('\n');
|
|
if (embeddedMpvRequired) {
|
|
throw new Error(message);
|
|
}
|
|
|
|
log(message);
|
|
return;
|
|
}
|
|
|
|
const runtimeManifest = copyRuntimeToNativeBuild({
|
|
runtimeLibDir: runtime.libDir,
|
|
outputLibDir,
|
|
runtimeOrigin: runtime.origin,
|
|
runtimeManifest: {
|
|
targetArch,
|
|
...runtime.manifest,
|
|
},
|
|
});
|
|
fs.rmSync(unavailableMarkerFile, { force: true });
|
|
|
|
const electronPackageJson = require(path.join(
|
|
workspaceRoot,
|
|
'node_modules',
|
|
'electron',
|
|
'package.json'
|
|
));
|
|
const electronVersion = electronPackageJson.version;
|
|
const env = {
|
|
...process.env,
|
|
npm_config_runtime: 'electron',
|
|
npm_config_target: electronVersion,
|
|
npm_config_arch: targetArch,
|
|
npm_config_disturl: 'https://electronjs.org/headers',
|
|
npm_config_build_from_source: 'true',
|
|
npm_config_update_binary: 'false',
|
|
LIBMPV_INCLUDE_DIR: runtime.includeDir,
|
|
LIBMPV_LIBRARY_DIR: outputLibDir,
|
|
};
|
|
|
|
log(
|
|
`Building native addon against Electron ${electronVersion} using ${runtime.origin} runtime for darwin-${targetArch}...`
|
|
);
|
|
runNodeGyp('configure', env);
|
|
runNodeGyp('build', env);
|
|
|
|
if (!fs.existsSync(outputFile)) {
|
|
throw new Error(`Build finished without producing ${outputFile}.`);
|
|
}
|
|
|
|
patchAddonForBundledRuntime(outputFile, outputLibDir);
|
|
const forbiddenLinkErrors = validateNoForbiddenRuntimeLinks([
|
|
outputFile,
|
|
...runtimeManifest.dylibs.map((dylib) => path.join(outputLibDir, dylib)),
|
|
]);
|
|
if (runtime.origin === 'vendored-lgpl' && forbiddenLinkErrors.length > 0) {
|
|
throw new Error(forbiddenLinkErrors.join('\n'));
|
|
}
|
|
|
|
log(`Built ${path.relative(workspaceRoot, outputFile)}.`);
|
|
}
|
|
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
process.stderr.write(
|
|
`[embedded-mpv] ${error instanceof Error ? error.message : String(error)}\n`
|
|
);
|
|
process.exit(1);
|
|
}
|