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