diff --git a/nss/meson.build b/nss/meson.build new file mode 100644 --- /dev/null +++ b/nss/meson.build @@ -0,0 +1,147 @@ +project('nss', 'c') + +py = import('python').find_installation('python3') +sh = find_program('sh') + +buildtype = get_option('buildtype') +if buildtype == 'debug' + nss_target = 'Debug' +else + nss_target = 'Release' +endif + +build_args = [] +if buildtype != 'debug' + build_args += ['--opt'] +endif +if not get_option('build_tests') + build_args += ['--disable-tests'] +endif +if get_option('enable_fips') + build_args += ['--enable-fips'] +endif +if get_option('enable_libpkix') + build_args += ['--enable-libpkix'] +endif +if get_option('enable_legacy_db') + build_args += ['--enable-legacy-db'] +endif +if get_option('system_sqlite') + build_args += ['--system-sqlite'] +endif + +nspr_version = get_option('nspr_version') +if nspr_version == 'auto' + nspr_dep = dependency('nspr', required : false) + if not nspr_dep.found() + nspr_dep = dependency('nspr4', required : false) + endif + if not nspr_dep.found() + error('Unable to determine the NSPR version. Install pkg-config metadata for NSPR or configure with -Dnspr_version=...') + endif + nspr_version = nspr_dep.version() +endif + +version_cmd = [ + '-c', + '''import pathlib,re,sys +text = pathlib.Path(sys.argv[1]).read_text() +match = re.search(r'#define\s+NSS_VERSION\s+"([^"]+)"', text) +if not match: + raise SystemExit('unable to parse NSS_VERSION') +version = match.group(1) +parts = (version.split('.') + ['0', '0'])[:3] +print(version) +print(parts[0]) +print(parts[1]) +print(parts[2]) +''', + join_paths(meson.current_source_dir(), 'lib', 'nss', 'nss.h'), +] +version_info = run_command(py, version_cmd, check : true).stdout().strip().split('\n') +nss_version = version_info[0] +nss_major_version = version_info[1] +nss_minor_version = version_info[2] +nss_patch_version = version_info[3] + +prefix = get_option('prefix') +exec_prefix = prefix +libdir = join_paths(prefix, get_option('libdir')) +includedir = join_paths(prefix, get_option('includedir'), 'nss') + +pkgconf = configuration_data() +pkgconf.set('prefix', prefix) +pkgconf.set('exec_prefix', exec_prefix) +pkgconf.set('libdir', libdir) +pkgconf.set('includedir', includedir) +pkgconf.set('NSS_VERSION', nss_version) +pkgconf.set('NSPR_VERSION', nspr_version) + +scriptconf = configuration_data() +scriptconf.set('prefix', prefix) +scriptconf.set('MOD_MAJOR_VERSION', nss_major_version) +scriptconf.set('MOD_MINOR_VERSION', nss_minor_version) +scriptconf.set('MOD_PATCH_VERSION', nss_patch_version) + +configure_file( + input : 'meson/nss.pc.in', + output : 'nss.pc', + configuration : pkgconf, + install : true, + install_dir : join_paths(get_option('libdir'), 'pkgconfig'), +) + +configure_file( + input : 'meson/nss-config.in', + output : 'nss-config', + configuration : scriptconf, + install : true, + install_dir : get_option('bindir'), + install_mode : 'rwxr-xr-x', +) + +nss_build_root = join_paths(meson.current_build_dir(), 'nss-build') + +custom_target( + 'nss-build', + output : 'nss-build.stamp', + command : [ + sh, + files('meson/build_nss.sh'), + '--source-dir', + meson.current_source_dir(), + '--build-root', + nss_build_root, + '--gyp', + get_option('gyp'), + '--nspr', + get_option('nspr'), + '--stamp', + '@OUTPUT@', + '--', + ] + build_args, + build_by_default : true, + build_always_stale : true, + console : true, +) + +meson.add_install_script( + py, + files('meson/install_nss.py'), + '--build-root', + nss_build_root, + '--target', + nss_target, + '--libdir', + get_option('libdir'), + '--bindir', + get_option('bindir'), + '--includedir', + get_option('includedir'), + '--install-private-headers', + get_option('install_private_headers') ? 'true' : 'false', + '--install-tools', + get_option('install_tools') ? 'true' : 'false', + '--install-ckbi', + get_option('install_ckbi') ? 'true' : 'false', +) diff --git a/nss/meson_options.txt b/nss/meson_options.txt new file mode 100644 --- /dev/null +++ b/nss/meson_options.txt @@ -0,0 +1,66 @@ +option( + 'build_tests', + type : 'boolean', + value : false, + description : 'Build NSS tests and the extra test-only helper binaries' +) +option( + 'enable_fips', + type : 'boolean', + value : false, + description : 'Enable FIPS checks in the GYP build' +) +option( + 'enable_libpkix', + type : 'boolean', + value : false, + description : 'Build libpkix support' +) +option( + 'enable_legacy_db', + type : 'boolean', + value : false, + description : 'Build the legacy DB module' +) +option( + 'system_sqlite', + type : 'boolean', + value : false, + description : 'Link against the system sqlite library' +) +option( + 'install_private_headers', + type : 'boolean', + value : false, + description : 'Install headers from dist/private/nss in addition to the public API' +) +option( + 'install_tools', + type : 'boolean', + value : true, + description : 'Install the NSS command-line tools from dist//bin' +) +option( + 'install_ckbi', + type : 'boolean', + value : false, + description : 'Install libnssckbi.so; disable by default to avoid conflicts with p11-kit trust modules' +) +option( + 'gyp', + type : 'string', + value : 'auto', + description : 'Path to the gyp executable, or auto to resolve gyp/gyp-next from PATH' +) +option( + 'nspr', + type : 'string', + value : 'auto', + description : 'NSPR include:lib pair for build.sh, or auto to resolve it with pkg-config' +) +option( + 'nspr_version', + type : 'string', + value : 'auto', + description : 'NSPR version string to embed in nss.pc when pkg-config metadata is unavailable' +) diff --git a/nss/meson/build_nss.sh b/nss/meson/build_nss.sh new file mode 100644 --- /dev/null +++ b/nss/meson/build_nss.sh @@ -0,0 +1,243 @@ +#!/bin/sh + +set -eu + +source_dir= +build_root= +gyp_bin=auto +nspr=auto +stamp= +build_args= + +append_arg() { + if [ -n "$build_args" ]; then + build_args="$build_args +$1" + else + build_args=$1 + fi +} + +while [ $# -gt 0 ]; do + case "$1" in + --source-dir) + source_dir=$2 + shift 2 + ;; + --build-root) + build_root=$2 + shift 2 + ;; + --gyp) + gyp_bin=$2 + shift 2 + ;; + --nspr) + nspr=$2 + shift 2 + ;; + --stamp) + stamp=$2 + shift 2 + ;; + --) + shift + break + ;; + *) + echo "Unknown option: $1" >&2 + exit 2 + ;; + esac +done + +while [ $# -gt 0 ]; do + append_arg "$1" + shift +done + +if [ -z "$source_dir" ] || [ -z "$build_root" ] || [ -z "$stamp" ]; then + echo "Missing required arguments" >&2 + exit 2 +fi + +resolve_gyp() { + if [ "$gyp_bin" != "auto" ]; then + echo "$gyp_bin" + return 0 + fi + + if [ -n "${GYP:-}" ]; then + echo "$GYP" + return 0 + fi + + if command -v gyp >/dev/null 2>&1; then + command -v gyp + return 0 + fi + + if command -v gyp-next >/dev/null 2>&1; then + command -v gyp-next + return 0 + fi + + echo "Unable to find gyp. Configure Meson with -Dgyp=/path/to/gyp." >&2 + exit 1 +} + +pkg_config_var() { + package=$1 + variable=$2 + if pkg-config --exists "$package" 2>/dev/null; then + pkg-config --variable="$variable" "$package" + return 0 + fi + return 1 +} + +resolve_nspr() { + if [ "$nspr" != "auto" ]; then + case "$nspr" in + *:*) + echo "$nspr" + return 0 + ;; + *) + echo "The -Dnspr option must use the form :." >&2 + exit 1 + ;; + esac + fi + + for package in nspr nspr4; do + include_dir=$(pkg_config_var "$package" includedir || true) + lib_dir=$(pkg_config_var "$package" libdir || true) + if [ -n "$include_dir" ] && [ -n "$lib_dir" ]; then + echo "$include_dir:$lib_dir" + return 0 + fi + done + + echo "Unable to resolve NSPR. Configure Meson with -Dnspr=:." >&2 + exit 1 +} + +detect_jobs() { + if [ -n "${MESON_NUM_PROCESSES:-}" ]; then + echo "$MESON_NUM_PROCESSES" + return 0 + fi + + if command -v getconf >/dev/null 2>&1; then + jobs=$(getconf _NPROCESSORS_ONLN 2>/dev/null || true) + if [ -n "$jobs" ]; then + echo "$jobs" + return 0 + fi + fi + + echo 1 +} + +detect_target_arch() { + "${PYTHON:-python3}" "$source_dir/coreconf/detect_host_arch.py" +} + +gyp_bin=$(resolve_gyp) +nspr=$(resolve_nspr) +jobs=$(detect_jobs) +target_arch=$(detect_target_arch) +target=Debug +disable_tests=0 +enable_fips=0 +enable_libpkix=0 +enable_legacy_db=0 +system_sqlite=0 + +if [ -n "$build_args" ]; then + old_ifs=$IFS + IFS=' +' + for arg in $build_args; do + case "$arg" in + --opt) + target=Release + ;; + --disable-tests) + disable_tests=1 + ;; + --enable-fips) + enable_fips=1 + ;; + --enable-libpkix) + enable_libpkix=1 + ;; + --enable-legacy-db) + enable_legacy_db=1 + ;; + --system-sqlite) + system_sqlite=1 + ;; + *) + echo "Unsupported build option: $arg" >&2 + exit 1 + ;; + esac + done + IFS=$old_ifs +fi + +dist_dir=$build_root/dist +out_dir=$build_root/out +target_dir=$out_dir/$target +mkdir -p "$dist_dir" "$target_dir" + +nspr_include_dir=${nspr%%:*} +nspr_lib_dir=${nspr#*:} + +set -- \ + --depth="$source_dir" \ + --generator-output="$build_root" \ + -Dtarget_arch="$target_arch" \ + -Dnss_dist_dir="$dist_dir" \ + -Dnss_dist_obj_dir="$dist_dir/$target" \ + -Dnspr_include_dir="$nspr_include_dir" \ + -Dnspr_lib_dir="$nspr_lib_dir" \ + -Denable_sslkeylogfile=1 + +if [ "$disable_tests" = 1 ]; then + set -- "$@" -Ddisable_tests=1 +fi +if [ "$enable_fips" = 1 ]; then + set -- "$@" -Ddisable_fips=0 +fi +if [ "$enable_libpkix" = 1 ]; then + set -- "$@" -Ddisable_libpkix=0 +fi +if [ "$enable_legacy_db" = 1 ]; then + set -- "$@" -Ddisable_dbm=0 +fi +if [ "$system_sqlite" = 1 ]; then + set -- "$@" -Duse_system_sqlite=1 +fi + +( + cd "$source_dir" + "$gyp_bin" -f ninja "$@" "$source_dir/nss.gyp" +) +if command -v ninja-build >/dev/null 2>&1; then + ninja_bin=$(command -v ninja-build) +elif command -v ninja >/dev/null 2>&1; then + ninja_bin=$(command -v ninja) +else + echo "Unable to find ninja." >&2 + exit 1 +fi + +( + cd "$source_dir" + "$ninja_bin" -C "$target_dir" -j "$jobs" +) + +printf '%s\n' "$build_root" >"$stamp" diff --git a/nss/meson/install_nss.py b/nss/meson/install_nss.py new file mode 100644 --- /dev/null +++ b/nss/meson/install_nss.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +import argparse +import os +import pathlib +import shutil + + +def bool_arg(value: str) -> bool: + return value.lower() in {'1', 'true', 'yes', 'on'} + + +def install_root(prefix: pathlib.Path, path: str) -> pathlib.Path: + candidate = pathlib.Path(path) + if candidate.is_absolute(): + return candidate + return prefix / candidate + + +def install_entry(src: pathlib.Path, dst: pathlib.Path) -> None: + dst.parent.mkdir(parents=True, exist_ok=True) + if dst.exists() or dst.is_symlink(): + if dst.is_dir() and not dst.is_symlink(): + shutil.rmtree(dst) + else: + dst.unlink() + + if src.is_symlink(): + dst.symlink_to(os.readlink(src)) + elif src.is_dir(): + shutil.copytree(src, dst, symlinks=True) + else: + shutil.copy2(src, dst) + + +def install_tree(src: pathlib.Path, dst: pathlib.Path) -> None: + if not src.exists(): + return + + for entry in sorted(src.iterdir()): + install_entry(entry, dst / entry.name) + + +def install_filtered_libs(src: pathlib.Path, dst: pathlib.Path, install_ckbi: bool) -> None: + if not src.exists(): + return + + for entry in sorted(src.iterdir()): + if entry.name.endswith('.TOC'): + continue + if not install_ckbi and entry.name.startswith('libnssckbi.so'): + continue + if entry.suffix in {'.a', '.chk', '.so'} or '.so.' in entry.name: + install_entry(entry, dst / entry.name) + + +def install_tools(src: pathlib.Path, dst: pathlib.Path) -> None: + if not src.exists(): + return + + for entry in sorted(src.iterdir()): + if entry.is_dir(): + continue + if os.access(entry, os.X_OK): + install_entry(entry, dst / entry.name) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument('--build-root', required=True) + parser.add_argument('--target', required=True) + parser.add_argument('--libdir', required=True) + parser.add_argument('--bindir', required=True) + parser.add_argument('--includedir', required=True) + parser.add_argument('--install-private-headers', required=True) + parser.add_argument('--install-tools', required=True) + parser.add_argument('--install-ckbi', required=True) + args = parser.parse_args() + + install_prefix = pathlib.Path( + os.environ.get('MESON_INSTALL_DESTDIR_PREFIX', os.environ['MESON_INSTALL_PREFIX']) + ) + libdir = install_root(install_prefix, args.libdir) + bindir = install_root(install_prefix, args.bindir) + includedir = install_root(install_prefix, args.includedir) + + build_root = pathlib.Path(args.build_root) + dist_root = build_root / 'dist' + public_headers = dist_root / 'public' / 'nss' + private_headers = dist_root / 'private' / 'nss' + target_root = dist_root / args.target + + install_tree(public_headers, includedir / 'nss') + if bool_arg(args.install_private_headers): + install_tree(private_headers, includedir / 'nss-private') + + install_filtered_libs(target_root / 'lib', libdir, bool_arg(args.install_ckbi)) + if bool_arg(args.install_tools): + install_tools(target_root / 'bin', bindir) + + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/nss/meson/nss-config.in b/nss/meson/nss-config.in new file mode 100644 --- /dev/null +++ b/nss/meson/nss-config.in @@ -0,0 +1,144 @@ +#!/bin/sh + +prefix=@prefix@ + +major_version=@MOD_MAJOR_VERSION@ +minor_version=@MOD_MINOR_VERSION@ +patch_version=@MOD_PATCH_VERSION@ + +usage() +{ + cat <&2 +fi + +lib_ssl=yes +lib_smime=yes +lib_nss=yes +lib_nssutil=yes + +while test $# -gt 0; do + case "$1" in + -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) optarg= ;; + esac + + case $1 in + --prefix=*) + prefix=$optarg + ;; + --prefix) + echo_prefix=yes + ;; + --exec-prefix=*) + exec_prefix=$optarg + ;; + --exec-prefix) + echo_exec_prefix=yes + ;; + --includedir=*) + includedir=$optarg + ;; + --includedir) + echo_includedir=yes + ;; + --libdir=*) + libdir=$optarg + ;; + --libdir) + echo_libdir=yes + ;; + --version) + echo ${major_version}.${minor_version}.${patch_version} + ;; + --cflags) + echo_cflags=yes + ;; + --libs) + echo_libs=yes + ;; + ssl) + lib_ssl=yes + ;; + smime) + lib_smime=yes + ;; + nss) + lib_nss=yes + ;; + nssutil) + lib_nssutil=yes + ;; + *) + usage 1 1>&2 + ;; + esac + shift +done + +# Set variables that may be dependent upon other variables +if test -z "$exec_prefix"; then + exec_prefix=`pkg-config --variable=exec_prefix nss` +fi +if test -z "$includedir"; then + includedir=`pkg-config --variable=includedir nss` +fi +if test -z "$libdir"; then + libdir=`pkg-config --variable=libdir nss` +fi + +if test "$echo_prefix" = "yes"; then + echo $prefix +fi + +if test "$echo_exec_prefix" = "yes"; then + echo $exec_prefix +fi + +if test "$echo_includedir" = "yes"; then + echo $includedir +fi + +if test "$echo_libdir" = "yes"; then + echo $libdir +fi + +if test "$echo_cflags" = "yes"; then + echo -I$includedir +fi + +if test "$echo_libs" = "yes"; then + libdirs="-Wl,-rpath-link,$libdir -L$libdir" + if test -n "$lib_ssl"; then + libdirs="$libdirs -lssl${major_version}" + fi + if test -n "$lib_smime"; then + libdirs="$libdirs -lsmime${major_version}" + fi + if test -n "$lib_nss"; then + libdirs="$libdirs -lnss${major_version}" + fi + if test -n "$lib_nssutil"; then + libdirs="$libdirs -lnssutil${major_version}" + fi + echo $libdirs +fi diff --git a/nss/meson/nss.pc.in b/nss/meson/nss.pc.in new file mode 100644 --- /dev/null +++ b/nss/meson/nss.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: NSS +Description: Network Security Services +Version: @NSS_VERSION@ +Requires: nspr >= @NSPR_VERSION@ +Libs: -L${libdir} -lssl3 -lsmime3 -lnss3 -lnssutil3 +Cflags: -I${includedir} diff --git a/nss/readme.md b/nss/readme.md --- a/nss/readme.md +++ b/nss/readme.md @@ -44,6 +44,23 @@ See [help.txt](https://hg.mozilla.org/projects/nss/raw-file/tip/help.txt) for more information on using build.sh. +## Building and Installing with Meson + +NSS also ships a Meson wrapper that drives the existing `build.sh`/GYP build +and installs the resulting headers, libraries, tools, `nss.pc`, and +`nss-config`. + + meson setup build -Dgyp=/path/to/gyp + meson compile -C build + meson install -C build + +If NSPR is not available via `pkg-config`, pass it explicitly as +`-Dnspr=/path/to/include:/path/to/lib`. Meson keeps the GYP output inside the +Meson build tree and installs public headers to `${includedir}/nss`. The +Meson install defaults to skipping `libnssckbi.so` to avoid file conflicts +with `p11-kit` trust module packages; enable `-Dinstall_ckbi=true` if you +explicitly want NSS to install that module. + ## Building NSS (legacy build system) After changing into the NSS directory a typical build of 32-bit NSS is done as