[gnome-builder/wip/tingping/meson] meson: Initial plugin
- From: Patrick Griffis <pgriffis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/tingping/meson] meson: Initial plugin
- Date: Sat, 29 Oct 2016 15:51:45 +0000 (UTC)
commit 2e7a42949b070c0e39fae5f99a356f7c21348c2c
Author: Patrick Griffis <tingping tingping se>
Date: Wed Aug 17 16:42:48 2016 -0400
meson: Initial plugin
This should support everything:
- Building
- Installing
- Targets (Running)
- Getting compile flags (Clang)
https://bugzilla.gnome.org/show_bug.cgi?id=743280
configure.ac | 2 +
plugins/meson/Makefile.am | 14 ++
plugins/meson/configure.ac | 12 +
plugins/meson/meson.plugin | 11 +
plugins/meson/meson_plugin/__init__.py | 370 ++++++++++++++++++++++++++++++++
5 files changed, 409 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 61b6bbf..e0bd388 100644
--- a/configure.ac
+++ b/configure.ac
@@ -304,6 +304,7 @@ m4_include([plugins/html-completion/configure.ac])
m4_include([plugins/html-preview/configure.ac])
m4_include([plugins/jedi/configure.ac])
m4_include([plugins/jhbuild/configure.ac])
+m4_include([plugins/meson/configure.ac])
m4_include([plugins/mingw/configure.ac])
m4_include([plugins/project-tree/configure.ac])
m4_include([plugins/python-gi-imports-completion/configure.ac])
@@ -612,6 +613,7 @@ echo " GNOME Code Assistance ................ : ${enable_gnome_code_assistance_
echo " HTML Autocompletion .................. : ${enable_html_completion_plugin}"
echo " HTML and Markdown Preview ............ : ${enable_html_preview_plugin}"
echo " JHBuild .............................. : ${enable_jhbuild_plugin}"
+echo " Meson ................................ : ${enable_meson_plugin}"
echo " MinGW ................................ : ${enable_mingw_plugin}"
echo " Project Creation ..................... : ${enable_create_project_plugin}"
echo " Project Tree ......................... : ${enable_project_tree_plugin}"
diff --git a/plugins/meson/Makefile.am b/plugins/meson/Makefile.am
new file mode 100644
index 0000000..a38b3ac
--- /dev/null
+++ b/plugins/meson/Makefile.am
@@ -0,0 +1,14 @@
+if ENABLE_MESON_PLUGIN
+
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+dist_plugin_DATA = meson.plugin
+
+moduledir = $(libdir)/gnome-builder/plugins/meson_plugin
+dist_module_DATA = meson_plugin/__init__.py
+
+endif
+
+-include $(top_srcdir)/git.mk
+
diff --git a/plugins/meson/configure.ac b/plugins/meson/configure.ac
new file mode 100644
index 0000000..6950ae2
--- /dev/null
+++ b/plugins/meson/configure.ac
@@ -0,0 +1,12 @@
+# --enable-meson-plugin=yes/no
+AC_ARG_ENABLE([meson-plugin],
+ [AS_HELP_STRING([--enable-meson-plugin=@<:@yes/no@:>@],
+ [Build with support for the Meson build system])],
+ [enable_meson_plugin=$enableval],
+ [enable_meson_plugin=yes])
+
+# for if ENABLE_MESON_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_MESON_PLUGIN, test x$enable_python_scripting = xyes && test x$enable_meson_plugin =
xyes)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/meson/Makefile])
diff --git a/plugins/meson/meson.plugin b/plugins/meson/meson.plugin
new file mode 100644
index 0000000..081a4b9
--- /dev/null
+++ b/plugins/meson/meson.plugin
@@ -0,0 +1,11 @@
+[Plugin]
+Module=meson_plugin
+Loader=python3
+Name=Meson
+Description=Provides integration with the Meson build system
+Authors=Patrick Griffis <tingping tingping se>
+Copyright=Copyright © 2016 Patrick Griffis
+Builtin=true
+Hidden=true
+X-Project-File-Filter-Pattern=meson.build
+X-Project-File-Filter-Name=Meson Project (meson.build)
diff --git a/plugins/meson/meson_plugin/__init__.py b/plugins/meson/meson_plugin/__init__.py
new file mode 100644
index 0000000..de9d0d6
--- /dev/null
+++ b/plugins/meson/meson_plugin/__init__.py
@@ -0,0 +1,370 @@
+# __init__.py
+#
+# Copyright (C) 2016 Patrick Griffis <tingping tingping se>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from os import path
+import subprocess
+import threading
+import shutil
+import json
+import gi
+
+gi.require_version('Ide', '1.0')
+
+from gi.repository import (
+ GLib,
+ GObject,
+ Gio,
+ Ide
+)
+
+_ = Ide.gettext
+
+ninja = None
+
+
+class MesonBuildSystem(Ide.Object, Ide.BuildSystem, Gio.AsyncInitable):
+ project_file = GObject.Property(type=Gio.File)
+
+ def do_init_async(self, priority, cancel, callback, data=None):
+ task = Gio.Task.new(self, cancel, callback)
+ task.set_priority(priority)
+
+ self._cached_config = None
+ self._cached_builder = None
+
+ # TODO: Be async here also
+ project_file = self.get_context().get_project_file()
+ if project_file.get_basename() == 'meson.build':
+ task.return_boolean(True)
+ else:
+ child = project_file.get_child('meson.build')
+ exists = child.query_exists(cancel)
+ if exists:
+ self.props.project_file = child
+ task.return_boolean(exists)
+
+ def do_init_finish(self, result):
+ return result.propagate_boolean()
+
+ def do_get_priority(self):
+ return -200 # Lower priority than Autotools for now
+
+ def do_get_builder(self, config):
+ if config == self._cached_config:
+ return self._cached_builder
+ else:
+ self._cached_config = config
+ self._cached_builder = MesonBuilder(context=self.get_context(), configuration=config)
+ return self._cached_builder
+
+ def do_get_build_flags_async(self, ifile, cancellable, callback, data=None):
+ task = Gio.Task.new(self, cancellable, callback)
+ task.build_flags = []
+
+ config = self._cached_config
+ builder = self._cached_builder
+
+ # FIXME: task.return_boolean(False) segfaults?
+ if not config:
+ task.return_error(GLib.Error('Project must be built before we can get flags'))
+ return
+
+ def extract_flags(command: str):
+ flags = GLib.shell_parse_argv(command)[1] # Raises on failure
+ return [flag for flag in flags if flag.startswith(('-I', '-isystem', '-W', '-D'))]
+
+ def build_flags_thread():
+ commands_file = path.join(builder._get_build_dir().get_path(), 'compile_commands.json')
+ try:
+ with open(commands_file) as f:
+ commands = json.loads(f.read(), encoding='utf-8')
+ except (json.JSONDecodeError, FileNotFoundError):
+ task.return_error(GLib.Error('Failed to decode meson json'))
+ return
+
+ infile = ifile.get_path()
+ for c in commands:
+ filepath = path.normpath(path.join(c['directory'], c['file']))
+ if filepath == infile:
+ try:
+ task.build_flags = extract_flags(c['command'])
+ except GLib.Error as e:
+ task.return_error(e)
+ return
+ break
+ else:
+ print('Meson: Warning: No flags found')
+
+ task.return_boolean(True)
+
+ thread = threading.Thread(target=build_flags_thread)
+ thread.start()
+
+ def do_get_build_flags_finish(self, result):
+ if result.propagate_boolean():
+ return result.build_flags
+ return []
+
+ def do_get_build_targets_async(self, cancellable, callback, data=None):
+ task = Gio.Task.new(self, cancellable, callback)
+ task.build_targets = []
+
+ # TODO: Cleaner API for this?
+ config = self._cached_config
+ builder = self._cached_builder
+
+ def build_targets_thread():
+ # TODO: Ide.Subprocess.communicate_utf8(None, cancellable) doesn't work?
+ try:
+ ret = subprocess.check_output(['mesonintrospect', '--targets',
+ builder._get_build_dir().get_path()])
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ task.return_error(GLib.Error('Failed to run mesonintrospect'))
+ task.return_boolean(False)
+ return
+
+ targets = []
+ try:
+ meson_targets = json.loads(ret.decode('utf-8'))
+ except json.JSONDecodeError:
+ task.return_error(GLib.Error('Failed to decode meson json'))
+ task.return_boolean(False)
+ return
+
+ bindir = path.join(config.get_prefix(), 'bin')
+ for t in meson_targets:
+ name = t['filename']
+ if isinstance(name, list):
+ name = name[0]
+
+ install_dir = t.get('install_filename', '')
+ installed = t['installed']
+ if installed and not install_dir:
+ print('Meson: Warning: Older versions of Meson did not expose install dir')
+ if t['type'] == 'executable':
+ # Hardcode bad guess
+ install_dir = bindir
+ elif install_dir:
+ install_dir = path.dirname(install_dir)
+
+ ide_target = MesonBuildTarget(install_dir, name=name)
+ # Try to be smart and sort these because Builder runs the
+ # first one. Ideally it allows the user to select the run targets.
+ if t['type'] == 'executable' and installed and \
+ install_dir.startswith(bindir) and not t['filename'].endswith('-cli'):
+ targets.insert(0, ide_target)
+ else:
+ targets.append(ide_target)
+
+ task.build_targets = targets
+ task.return_boolean(True)
+
+ thread = threading.Thread(target=build_targets_thread)
+ thread.start()
+
+ def do_get_build_targets_finish(self, result):
+ if result.propagate_boolean():
+ return result.build_targets
+
+
+class MesonBuilder(Ide.Builder):
+ configuration = GObject.Property(type=Ide.Configuration)
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+
+ def _get_build_dir(self) -> Gio.File:
+ context = self.get_context()
+
+ # This matches the Autotools layout
+ project_id = context.get_project().get_id()
+ buildroot = context.get_root_build_dir()
+ device = self.props.configuration.get_device()
+ device_id = device.get_id()
+ system_type = device.get_system_type()
+
+ return Gio.File.new_for_path(path.join(buildroot, project_id, device_id, system_type))
+
+ def _get_source_dir(self) -> Gio.File:
+ context = self.get_context()
+ return context.get_vcs().get_working_directory()
+
+ def do_build_async(self, flags, cancellable, callback, data=None):
+ task = Gio.Task.new(self, cancellable, callback)
+ task.build_result = MesonBuildResult(self.configuration,
+ self._get_build_dir(),
+ self._get_source_dir(),
+ cancellable,
+ flags=flags)
+
+ def wrap_build():
+ task.build_result.set_running(True)
+ try:
+ task.build_result.build()
+ task.build_result.set_mode(_('Successful'))
+ task.build_result.set_failed(False)
+ task.return_boolean(True)
+ except GLib.Error as e:
+ task.build_result.set_mode(_('Failed'))
+ task.build_result.set_failed(True)
+ task.return_error(e)
+ task.build_result.set_running(False)
+
+ thread = threading.Thread(target=wrap_build)
+ thread.start()
+
+ return task.build_result
+
+ def do_build_finish(self, result) -> Ide.BuildResult:
+ if result.propagate_boolean():
+ return result.build_result
+
+ def do_install_async(self, cancellable, callback, data=None):
+ task = Gio.Task.new(self, cancellable, callback)
+ task.build_result = MesonBuildResult(self.configuration,
+ self._get_build_dir(),
+ self._get_source_dir(),
+ cancellable)
+
+ def wrap_install():
+ task.build_result.set_running(True)
+ try:
+ task.build_result.install()
+ self = task.get_source_object()
+ task.build_result.set_mode(_('Successful'))
+ task.build_result.set_failed(False)
+ task.return_boolean(True)
+ except GLib.Error as e:
+ task.build_result.set_mode(_("Failed"))
+ task.build_result.set_failed(True)
+ task.return_error(e)
+ task.build_result.set_running(False)
+
+ thread = threading.Thread(target=wrap_install)
+ thread.start()
+
+ return task.build_result
+
+ def do_install_finish(self, result) -> Ide.BuildResult:
+ if result.propagate_boolean():
+ return result.build_result
+
+
+class MesonBuildResult(Ide.BuildResult):
+
+ def __init__(self, config, blddir, srcdir, cancel, flags=0, **kwargs):
+ super().__init__(**kwargs)
+ self.config = config
+ self.cancel = cancel
+ self.flags = flags
+ self.runtime = config.get_runtime()
+ self.blddir = blddir
+ self.srcdir = srcdir
+
+ def _new_launcher(self, cwd=None):
+ if self.runtime:
+ launcher = self.runtime.create_launcher()
+ else:
+ launcher = Ide.SubprocessLauncher.new(Gio.SubprocessFlags.NONE)
+ launcher.set_run_on_host(True)
+ launcher.set_clear_env(False)
+ if cwd:
+ launcher.set_cwd(cwd.get_path())
+ return launcher
+
+ def _get_ninja(self):
+ if not ninja:
+ global ninja
+ ninja = GLib.find_program_in_path('ninja-build')
+ if not ninja:
+ ninja = GLib.find_program_in_path('ninja')
+ if not ninja:
+ ninja = 'ninja'
+ return ninja
+
+ def _run_subprocess(self, launcher):
+ self.log_stdout_literal('Running: {}…'.format(' '.join(launcher.get_argv())))
+ proc = launcher.spawn()
+ self.log_subprocess(proc)
+ proc.wait_check(self.cancel)
+
+ def install(self):
+ launcher = self._new_launcher(cwd=self.blddir)
+ launcher.push_args([self._get_ninja(), 'install'])
+ self._run_subprocess(launcher)
+
+ def build(self):
+ """
+ NOTE: This is ran in a thread and it raising GLib.Error is handled a layer up.
+ """
+ clean = bool(self.flags & Ide.BuilderBuildFlags.FORCE_CLEAN)
+ build = not self.flags & Ide.BuilderBuildFlags.NO_BUILD
+ bootstrap = bool(self.flags & Ide.BuilderBuildFlags.FORCE_BOOTSTRAP)
+
+ self.log_stdout_literal('Starting Build…')
+ if bootstrap or self.config.get_dirty():
+ self.log_stdout_literal('Deleting build directory…')
+ try:
+ shutil.rmtree(self.blddir.get_path())
+ except FileNotFoundError:
+ pass
+ self.config.set_dirty(False)
+
+ if not self.blddir.query_exists():
+ self.log_stdout_literal('Creating build directory…')
+ self.blddir.make_directory_with_parents(self.cancel)
+
+ # TODO: For dirty config we could use `mesonconf` but it does not
+ # handle removing defines which might be unclear
+
+ if not self.blddir.get_child('build.ninja').query_exists():
+ self.log_stdout_literal('Running Meson…')
+ config_opts = self.config.get_config_opts()
+ extra_opts = config_opts.split() if config_opts else []
+ extra_opts.append('--prefix=' + self.config.get_prefix())
+ launcher = self._new_launcher(self.srcdir)
+ launcher.push_args(['meson', self.blddir.get_path()] + extra_opts)
+
+ self.set_mode(_('Configuring…'))
+ self._run_subprocess(launcher)
+
+ launcher = self._new_launcher(self.blddir)
+ launcher.push_args([self._get_ninja()])
+ if clean:
+ self.log_stdout_literal('Cleaning…')
+ self.set_mode(_('Cleaning…'))
+ launcher.push_args(['clean'])
+ self._run_subprocess(launcher)
+ if build:
+ if clean: # Build after cleaning
+ launcher.pop_argv()
+ self.log_stdout_literal('Building…')
+ self.set_mode(_('Building…'))
+ self._run_subprocess(launcher)
+
+class MesonBuildTarget(Ide.Object, Ide.BuildTarget):
+ # FIXME: These should be part of the BuildTarget interface
+ name = GObject.Property(type=str)
+ install_directory = GObject.Property(type=Gio.File)
+
+ def __init__(self, install_dir, **kwargs):
+ super().__init__(**kwargs)
+ self.props.install_directory = Gio.File.new_for_path(install_dir)
+
+ def do_get_install_directory(self):
+ return self.props.install_directory
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]