// SPDX-FileCopyrightText: 2012 Giovanni Campagna // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later /* exported checkSymbol, datadir, init, initFormat, initGettext, initSubmodule, libdir, localedir, moduledir, name, pkgdatadir, pkglibdir, prefix, require, requireSymbol, run, start, version */ /** * This module provides a set of convenience APIs for building packaged * applications. */ const GLib = imports.gi.GLib; const GIRepository = imports.gi.GIRepository; const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; const System = imports.system; const Gettext = imports.gettext; // public var name; var version; var prefix; var datadir; var libdir; var pkgdatadir; var pkglibdir; var moduledir; var localedir; // private let _pkgname; let _base; let _submoduledir; function _findEffectiveEntryPointName() { let entryPoint = System.programInvocationName; while (GLib.file_test(entryPoint, GLib.FileTest.IS_SYMLINK)) entryPoint = GLib.file_read_link(entryPoint); return GLib.path_get_basename(entryPoint); } function _runningFromSource() { let binary = Gio.File.new_for_path(System.programInvocationName); let sourceBinary = Gio.File.new_for_path(`./src/${name}`); return binary.equal(sourceBinary); } function _runningFromMesonSource() { return GLib.getenv('MESON_BUILD_ROOT') && GLib.getenv('MESON_SOURCE_ROOT'); } function _makeNamePath(n) { return `/${n.replace(/\./g, '/')}`; } /** * Initialize directories and global variables. Must be called * before any of other API in Package is used. * `params` must be an object with at least the following keys: * - name: the package name ($(PACKAGE_NAME) in autotools, * eg. org.foo.Bar) * - version: the package version * - prefix: the installation prefix * * init() will take care to check if the program is running from * the source directory or not, by looking for a 'src' directory. * * At the end, the global variable 'pkg' will contain the * Package module (imports.package). Additionally, the following * module variables will be available: * - name: the base name of the entry point (eg. org.foo.Bar.App) * - version: same as in @params * - prefix: the installation prefix (as passed in @params) * - datadir, libdir: the final datadir and libdir when installed; * usually, these would be prefix + '/share' and * and prefix + '/lib' (or '/lib64') * - pkgdatadir: the directory to look for private data files, such as * images, stylesheets and UI definitions; * this will be datadir + name when installed and * './data' when running from the source tree * - pkglibdir: the directory to look for private typelibs and C * libraries; * this will be libdir + name when installed and * './lib' when running from the source tree * - moduledir: the directory to look for JS modules; * this will be pkglibdir when installed and * './src' when running from the source tree * - localedir: the directory containing gettext translation files; * this will be datadir + '/locale' when installed * and './po' in the source tree * * All paths are absolute and will not end with '/'. * * As a side effect, init() calls GLib.set_prgname(). * * @param {object} params package parameters */ function init(params) { globalThis.pkg = imports.package; _pkgname = params.name; name = _findEffectiveEntryPointName(); version = params.version; // Must call it first, because it can only be called // once, and other library calls might have it as a // side effect GLib.set_prgname(name); prefix = params.prefix; libdir = params.libdir; datadir = GLib.build_filenamev([prefix, 'share']); let libpath, girpath; if (_runningFromMesonSource()) { log('Running from Meson, using local files'); let bld = GLib.getenv('MESON_BUILD_ROOT'); let src = GLib.getenv('MESON_SOURCE_ROOT'); pkglibdir = libpath = girpath = GLib.build_filenamev([bld, 'lib']); pkgdatadir = GLib.build_filenamev([bld, 'data']); localedir = GLib.build_filenamev([bld, 'po']); _submoduledir = GLib.build_filenamev([bld, 'subprojects']); GLib.setenv('GSETTINGS_SCHEMA_DIR', pkgdatadir, true); try { let resource = Gio.Resource.load(GLib.build_filenamev([bld, 'src', `${name}.src.gresource`])); resource._register(); moduledir = `resource://${_makeNamePath(name)}/js`; } catch (e) { moduledir = GLib.build_filenamev([src, 'src']); } } else if (_runningFromSource()) { log('Running from source tree, using local files'); // Running from source directory _base = GLib.get_current_dir(); _submoduledir = _base; pkglibdir = GLib.build_filenamev([_base, 'lib']); libpath = GLib.build_filenamev([pkglibdir, '.libs']); girpath = pkglibdir; pkgdatadir = GLib.build_filenamev([_base, 'data']); localedir = GLib.build_filenamev([_base, 'po']); moduledir = GLib.build_filenamev([_base, 'src']); GLib.setenv('GSETTINGS_SCHEMA_DIR', pkgdatadir, true); } else { _base = prefix; pkglibdir = GLib.build_filenamev([libdir, _pkgname]); libpath = pkglibdir; girpath = GLib.build_filenamev([pkglibdir, 'girepository-1.0']); pkgdatadir = GLib.build_filenamev([datadir, _pkgname]); localedir = GLib.build_filenamev([datadir, 'locale']); try { let resource = Gio.Resource.load(GLib.build_filenamev([pkgdatadir, `${name}.src.gresource`])); resource._register(); moduledir = `resource://${_makeNamePath(name)}/js`; } catch (e) { moduledir = pkgdatadir; } } imports.searchPath.unshift(moduledir); GIRepository.Repository.prepend_search_path(girpath); GIRepository.Repository.prepend_library_path(libpath); try { let resource = Gio.Resource.load(GLib.build_filenamev([pkgdatadir, `${name}.data.gresource`])); resource._register(); } catch (e) { } } /** * This is a convenience function if your package has a * single entry point. * You must define a main(ARGV) function inside a main.js * module in moduledir. * * @param {object} params see init() */ function start(params) { init(params); run(imports.main); } /** * This is the function to use if you want to have multiple * entry points in one package. * You must define a main(ARGV) function inside the passed * in module, and then the launcher would be * * imports.package.init(...); * imports.package.run(imports.entrypoint); * * @param {object} module the module to run * @returns {number|undefined} the exit code of the module's main() function */ function run(module) { return module.main([System.programInvocationName].concat(ARGV)); } /** * Mark a dependency on a specific version of one or more * external GI typelibs. * `libs` must be an object whose keys are a typelib name, * and values are the respective version. The empty string * indicates any version. * * @param {object} libs the external dependencies to import */ function require(libs) { for (let l in libs) requireSymbol(l, libs[l]); } /** * As checkSymbol(), but exit with an error if the * dependency cannot be satisfied. * * @param {string} lib an external dependency to import * @param {string} [ver] version of the dependency * @param {string} [symbol] symbol to check for */ function requireSymbol(lib, ver, symbol) { if (!checkSymbol(lib, ver, symbol)) { if (symbol) printerr(`Unsatisfied dependency: No ${symbol} in ${lib}`); else printerr(`Unsatisfied dependency: ${lib}`); System.exit(1); } } /** * Check whether an external GI typelib can be imported * and provides @symbol. * * Symbols may refer to * - global functions ('main_quit') * - classes ('Window') * - class / instance methods ('IconTheme.get_default' / 'IconTheme.has_icon') * - GObject properties ('Window.default_height') * * @param {string} lib an external dependency to import * @param {string} [ver] version of the dependency * @param {string} [symbol] symbol to check for * @returns {boolean} true if `lib` can be imported and provides `symbol`, false * otherwise */ function checkSymbol(lib, ver, symbol) { let Lib = null; if (ver) imports.gi.versions[lib] = ver; try { Lib = imports.gi[lib]; } catch (e) { return false; } if (!symbol) return true; // Done let [klass, sym] = symbol.split('.'); if (klass === symbol) return typeof Lib[symbol] !== 'undefined'; let obj = Lib[klass]; if (typeof obj === 'undefined') return false; if (typeof obj[sym] !== 'undefined' || obj.prototype && typeof obj.prototype[sym] !== 'undefined') return true; // class- or object method // GObject property let pspec = null; if (GObject.type_is_a(obj.$gtype, GObject.TYPE_INTERFACE)) { let iface = GObject.type_default_interface_ref(obj.$gtype); pspec = GObject.Object.interface_find_property(iface, sym); } else if (GObject.type_is_a(obj.$gtype, GObject.TYPE_OBJECT)) { pspec = GObject.Object.find_property.call(obj.$gtype, sym); } return pspec !== null; } function initGettext() { Gettext.bindtextdomain(_pkgname, localedir); Gettext.textdomain(_pkgname); let gettext = imports.gettext; globalThis._ = gettext.gettext; globalThis.C_ = gettext.pgettext; globalThis.N_ = function (x) { return x; }; } function initFormat() { // eslint-disable-next-line no-restricted-properties let format = imports.format; String.prototype.format = format.format; } function initSubmodule(moduleName) { if (_runningFromMesonSource() || _runningFromSource()) { // Running from source tree, add './moduleName' to search paths let submoduledir = GLib.build_filenamev([_submoduledir, moduleName]); let libpath; if (_runningFromMesonSource()) libpath = submoduledir; else libpath = GLib.build_filenamev([submoduledir, '.libs']); GIRepository.Repository.prepend_search_path(submoduledir); GIRepository.Repository.prepend_library_path(libpath); } else { // Running installed, submodule is in $(pkglibdir), nothing to do } }