Branch data Line data Source code
1 : 1 : // SPDX-FileCopyrightText: 2012 Giovanni Campagna
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : :
4 : : /* exported checkSymbol, datadir, init, initFormat, initGettext, initSubmodule,
5 : : libdir, localedir, moduledir, name, pkgdatadir, pkglibdir, prefix, require,
6 : : requireSymbol, run, start, version */
7 : :
8 : : /**
9 : : * This module provides a set of convenience APIs for building packaged
10 : : * applications.
11 : : */
12 : 1 : imports.gi.versions.GIRepository = '2.0';
13 : :
14 : 1 : const GLib = imports.gi.GLib;
15 : 1 : const GIRepository = imports.gi.GIRepository;
16 : 1 : const Gio = imports.gi.Gio;
17 : 1 : const GObject = imports.gi.GObject;
18 : 1 : const System = imports.system;
19 : :
20 : 1 : const Gettext = imports.gettext;
21 : :
22 : : // public
23 : : var name;
24 : : var version;
25 : : var prefix;
26 : : var datadir;
27 : : var libdir;
28 : : var pkgdatadir;
29 : : var pkglibdir;
30 : : var moduledir;
31 : : var localedir;
32 : :
33 : : // private
34 : 1 : let _pkgname;
35 : 1 : let _base;
36 : 1 : let _submoduledir;
37 : :
38 : 0 : function _findEffectiveEntryPointName() {
39 : 0 : let entryPoint = System.programInvocationName;
40 [ # # ]: 0 : while (GLib.file_test(entryPoint, GLib.FileTest.IS_SYMLINK))
41 : 0 : entryPoint = GLib.file_read_link(entryPoint);
42 : :
43 : 0 : return GLib.path_get_basename(entryPoint);
44 : 0 : }
45 : :
46 : 0 : function _runningFromSource() {
47 : 0 : let binary = Gio.File.new_for_path(System.programInvocationName);
48 : 0 : let sourceBinary = Gio.File.new_for_path(`./src/${name}`);
49 : 0 : return binary.equal(sourceBinary);
50 : 0 : }
51 : :
52 : 0 : function _runningFromMesonSource() {
53 [ # # ]: 0 : return GLib.getenv('MESON_BUILD_ROOT') &&
54 : 0 : GLib.getenv('MESON_SOURCE_ROOT');
55 : : }
56 : :
57 : : /**
58 : : * Initialize directories and global variables. Must be called
59 : : * before any of other API in Package is used.
60 : : * `params` must be an object with at least the following keys:
61 : : * - name: the package name ($(PACKAGE_NAME) in autotools,
62 : : * eg. org.foo.Bar)
63 : : * - version: the package version
64 : : * - prefix: the installation prefix
65 : : *
66 : : * init() will take care to check if the program is running from
67 : : * the source directory or not, by looking for a 'src' directory.
68 : : *
69 : : * At the end, the global variable 'pkg' will contain the
70 : : * Package module (imports.package). Additionally, the following
71 : : * module variables will be available:
72 : : * - name: the base name of the entry point (eg. org.foo.Bar.App)
73 : : * - version: same as in @params
74 : : * - prefix: the installation prefix (as passed in @params)
75 : : * - datadir, libdir: the final datadir and libdir when installed;
76 : : * usually, these would be prefix + '/share' and
77 : : * and prefix + '/lib' (or '/lib64')
78 : : * - pkgdatadir: the directory to look for private data files, such as
79 : : * images, stylesheets and UI definitions;
80 : : * this will be datadir + name when installed and
81 : : * './data' when running from the source tree
82 : : * - pkglibdir: the directory to look for private typelibs and C
83 : : * libraries;
84 : : * this will be libdir + name when installed and
85 : : * './lib' when running from the source tree
86 : : * - moduledir: the directory to look for JS modules;
87 : : * this will be pkglibdir when installed and
88 : : * './src' when running from the source tree
89 : : * - localedir: the directory containing gettext translation files;
90 : : * this will be datadir + '/locale' when installed
91 : : * and './po' in the source tree
92 : : *
93 : : * All paths are absolute and will not end with '/'.
94 : : *
95 : : * As a side effect, init() calls GLib.set_prgname().
96 : : *
97 : : * @param {object} params package parameters
98 : : */
99 : 0 : function init(params) {
100 : 0 : globalThis.pkg = imports.package;
101 : 0 : _pkgname = params.name;
102 : 0 : name = _findEffectiveEntryPointName();
103 : 0 : version = params.version;
104 : :
105 : : // Must call it first, because it can only be called
106 : : // once, and other library calls might have it as a
107 : : // side effect
108 : 0 : GLib.set_prgname(name);
109 : :
110 : 0 : prefix = params.prefix;
111 : 0 : libdir = params.libdir;
112 : 0 : datadir = GLib.build_filenamev([prefix, 'share']);
113 : 0 : let libpath, girpath;
114 : :
115 : 0 : const loadResource = (path, baseName) => {
116 : 0 : const resource = Gio.Resource.load(GLib.build_filenamev([path,
117 : 0 : `${baseName}.src.gresource`]));
118 : 0 : resource._register();
119 : :
120 : 0 : return `resource:///${baseName.replaceAll('.', '/')}/js`;
121 : 0 : };
122 : :
123 [ # # ]: 0 : if (_runningFromMesonSource()) {
124 : 0 : log('Running from Meson, using local files');
125 : 0 : let bld = GLib.getenv('MESON_BUILD_ROOT');
126 : :
127 : 0 : pkglibdir = libpath = girpath = GLib.build_filenamev([bld, 'lib']);
128 : 0 : pkgdatadir = GLib.build_filenamev([bld, 'data']);
129 : 0 : localedir = GLib.build_filenamev([bld, 'po']);
130 : 0 : _submoduledir = GLib.build_filenamev([bld, 'subprojects']);
131 : :
132 : 0 : GLib.setenv('GSETTINGS_SCHEMA_DIR', pkgdatadir, true);
133 : 0 : const bldPath = GLib.build_filenamev([bld, 'src']);
134 : 0 : try {
135 : 0 : moduledir = loadResource(bldPath, name);
136 : 0 : } catch (e) {
137 : 0 : try {
138 : 0 : moduledir = loadResource(bldPath, _pkgname);
139 : 0 : name = _pkgname;
140 : 0 : } catch {
141 : 0 : const src = GLib.getenv('MESON_SOURCE_ROOT');
142 : 0 : moduledir = GLib.build_filenamev([src, 'src']);
143 : : }
144 : : }
145 [ # # ]: 0 : } else if (_runningFromSource()) {
146 : 0 : log('Running from source tree, using local files');
147 : : // Running from source directory
148 : 0 : _base = GLib.get_current_dir();
149 : 0 : _submoduledir = _base;
150 : 0 : pkglibdir = GLib.build_filenamev([_base, 'lib']);
151 : 0 : libpath = GLib.build_filenamev([pkglibdir, '.libs']);
152 : 0 : girpath = pkglibdir;
153 : 0 : pkgdatadir = GLib.build_filenamev([_base, 'data']);
154 : 0 : localedir = GLib.build_filenamev([_base, 'po']);
155 : 0 : moduledir = GLib.build_filenamev([_base, 'src']);
156 : :
157 : 0 : GLib.setenv('GSETTINGS_SCHEMA_DIR', pkgdatadir, true);
158 : : } else {
159 : 0 : _base = prefix;
160 : 0 : pkglibdir = GLib.build_filenamev([libdir, _pkgname]);
161 : 0 : libpath = pkglibdir;
162 : 0 : girpath = GLib.build_filenamev([pkglibdir, 'girepository-1.0']);
163 : 0 : pkgdatadir = GLib.build_filenamev([datadir, _pkgname]);
164 : 0 : localedir = GLib.build_filenamev([datadir, 'locale']);
165 : :
166 : 0 : try {
167 : 0 : moduledir = loadResource(pkgdatadir, name);
168 : 0 : } catch (e) {
169 : 0 : try {
170 : 0 : moduledir = loadResource(pkgdatadir, _pkgname);
171 : 0 : name = _pkgname;
172 : 0 : } catch {
173 : 0 : moduledir = pkgdatadir;
174 : : }
175 : : }
176 : : }
177 : :
178 : 0 : imports.searchPath.unshift(moduledir);
179 : 0 : GIRepository.Repository.prepend_search_path(girpath);
180 : 0 : GIRepository.Repository.prepend_library_path(libpath);
181 : :
182 : 0 : try {
183 : 0 : let resource = Gio.Resource.load(GLib.build_filenamev([pkgdatadir,
184 : 0 : `${name}.data.gresource`]));
185 : 0 : resource._register();
186 : 0 : } catch (e) {
187 : 0 : try {
188 : 0 : let resource = Gio.Resource.load(GLib.build_filenamev([pkgdatadir,
189 : 0 : `${_pkgname}.data.gresource`]));
190 : 0 : resource._register();
191 : 0 : } catch {}
192 : : }
193 : : }
194 : :
195 : : /**
196 : : * This is a convenience function if your package has a
197 : : * single entry point.
198 : : * You must define a main(ARGV) function inside a main.js
199 : : * module in moduledir.
200 : : *
201 : : * @param {object} params see init()
202 : : */
203 : 0 : function start(params) {
204 : 0 : init(params);
205 : 0 : run(imports.main);
206 : : }
207 : :
208 : : /**
209 : : * This is the function to use if you want to have multiple
210 : : * entry points in one package.
211 : : * You must define a main(ARGV) function inside the passed
212 : : * in module, and then the launcher would be
213 : : *
214 : : * imports.package.init(...);
215 : : * imports.package.run(imports.entrypoint);
216 : : *
217 : : * @param {object} module the module to run
218 : : * @returns {number|undefined} the exit code of the module's main() function
219 : : */
220 : 0 : function run(module) {
221 : 0 : return module.main([System.programInvocationName].concat(ARGV));
222 : : }
223 : :
224 : : /**
225 : : * Mark a dependency on a specific version of one or more
226 : : * external GI typelibs.
227 : : * `libs` must be an object whose keys are a typelib name,
228 : : * and values are the respective version. The empty string
229 : : * indicates any version.
230 : : *
231 : : * @param {object} libs the external dependencies to import
232 : : */
233 : 0 : function require(libs) {
234 [ # # ]: 0 : for (let l in libs)
235 : 0 : requireSymbol(l, libs[l]);
236 : : }
237 : :
238 : : /**
239 : : * As checkSymbol(), but exit with an error if the
240 : : * dependency cannot be satisfied.
241 : : *
242 : : * @param {string} lib an external dependency to import
243 : : * @param {string} [ver] version of the dependency
244 : : * @param {string} [symbol] symbol to check for
245 : : */
246 : 0 : function requireSymbol(lib, ver, symbol) {
247 [ # # ]: 0 : if (!checkSymbol(lib, ver, symbol)) {
248 [ # # ]: 0 : if (symbol)
249 : 0 : printerr(`Unsatisfied dependency: No ${symbol} in ${lib}`);
250 : : else
251 : 0 : printerr(`Unsatisfied dependency: ${lib}`);
252 : 0 : System.exit(1);
253 : : }
254 : : }
255 : :
256 : : /**
257 : : * Check whether an external GI typelib can be imported
258 : : * and provides @symbol.
259 : : *
260 : : * Symbols may refer to
261 : : * - global functions ('main_quit')
262 : : * - classes ('Window')
263 : : * - class / instance methods ('IconTheme.get_default' / 'IconTheme.has_icon')
264 : : * - GObject properties ('Window.default_height')
265 : : *
266 : : * @param {string} lib an external dependency to import
267 : : * @param {string} [ver] version of the dependency
268 : : * @param {string} [symbol] symbol to check for
269 : : * @returns {boolean} true if `lib` can be imported and provides `symbol`, false
270 : : * otherwise
271 : : */
272 : 20 : function checkSymbol(lib, ver, symbol) {
273 : 20 : let Lib = null;
274 : :
275 [ - + ]: 20 : if (ver)
276 : 20 : imports.gi.versions[lib] = ver;
277 : :
278 : 20 : try {
279 : 20 : Lib = imports.gi[lib];
280 : 1 : } catch (e) {
281 : 1 : return false;
282 : : }
283 : :
284 [ + + ]: 19 : if (!symbol)
285 : 1 : return true; // Done
286 : :
287 [ - + ][ - + ]: 18 : let [klass, sym] = symbol.split('.');
[ + + ][ # # ]
[ # # ][ # # ]
[ # # ]
288 [ + + ]: 18 : if (klass === symbol)
289 : 8 : return typeof Lib[symbol] !== 'undefined';
290 : :
291 : 10 : let obj = Lib[klass];
292 [ + - ]: 10 : if (typeof obj === 'undefined')
293 : 0 : return false;
294 : :
295 [ + + ]: 10 : if (typeof obj[sym] !== 'undefined' ||
296 [ + + ][ + + ]: 7 : obj.prototype && typeof obj.prototype[sym] !== 'undefined')
297 : 5 : return true; // class- or object method
298 : :
299 : : // GObject property
300 : 5 : let pspec = null;
301 [ + + ]: 5 : if (GObject.type_is_a(obj.$gtype, GObject.TYPE_INTERFACE)) {
302 : 1 : let iface = GObject.type_default_interface_ref(obj.$gtype);
303 : 1 : pspec = GObject.Object.interface_find_property(iface, sym);
304 [ + + ]: 4 : } else if (GObject.type_is_a(obj.$gtype, GObject.TYPE_OBJECT)) {
305 : 3 : pspec = GObject.Object.find_property.call(obj.$gtype, sym);
306 : : }
307 : :
308 : 5 : return pspec !== null;
309 : 20 : }
310 : :
311 : 0 : function initGettext() {
312 : 0 : Gettext.bindtextdomain(_pkgname, localedir);
313 : 0 : Gettext.textdomain(_pkgname);
314 : :
315 : 0 : let gettext = imports.gettext;
316 : 0 : globalThis._ = gettext.gettext;
317 : 0 : globalThis.C_ = gettext.pgettext;
318 : 0 : globalThis.N_ = function (x) {
319 : 0 : return x;
320 : : };
321 : : }
322 : :
323 : 0 : function initFormat() {
324 : : // eslint-disable-next-line no-restricted-properties
325 : 0 : let format = imports.format;
326 : 0 : String.prototype.format = format.format;
327 : : }
328 : :
329 : 0 : function initSubmodule(moduleName) {
330 [ # # ][ # # ]: 0 : if (_runningFromMesonSource() || _runningFromSource()) {
331 : : // Running from source tree, add './moduleName' to search paths
332 : :
333 : 0 : let submoduledir = GLib.build_filenamev([_submoduledir, moduleName]);
334 : 0 : let libpath;
335 [ # # ]: 0 : if (_runningFromMesonSource())
336 : 0 : libpath = submoduledir;
337 : : else
338 : 0 : libpath = GLib.build_filenamev([submoduledir, '.libs']);
339 : 0 : GIRepository.Repository.prepend_search_path(submoduledir);
340 : 0 : GIRepository.Repository.prepend_library_path(libpath);
341 : : } else {
342 : : // Running installed, submodule is in $(pkglibdir), nothing to do
343 : : }
344 : : }
|