[gnome-builder] phpize: add phpize plugin for writing PHP extensions



commit d11be87ed791ac851ef18ef839a66488726562aa
Author: Christian Hergert <chergert redhat com>
Date:   Tue Mar 14 19:14:57 2017 -0700

    phpize: add phpize plugin for writing PHP extensions
    
    For the general purpose PHP extension case, where you're writing some C
    or C++ code for an extension.
    
    phpize is run to bootstrap the project, configure to configure, make to
    build, make clean to clean and make install to install.
    
    It looks for "config.m4" in the root of the project to determine that the
    project is a phpize project.

 configure.ac                    |    2 +
 plugins/Makefile.am             |    1 +
 plugins/phpize/Makefile.am      |   14 +++
 plugins/phpize/configure.ac     |   12 ++
 plugins/phpize/meson.build      |    6 +
 plugins/phpize/phpize.plugin    |   11 ++
 plugins/phpize/phpize_plugin.py |  244 +++++++++++++++++++++++++++++++++++++++
 7 files changed, 290 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 35aba9e..30c4c3d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -315,6 +315,7 @@ m4_include([plugins/meson/configure.ac])
 m4_include([plugins/meson-templates/configure.ac])
 m4_include([plugins/mingw/configure.ac])
 m4_include([plugins/mono/configure.ac])
+m4_include([plugins/phpize/configure.ac])
 m4_include([plugins/project-tree/configure.ac])
 m4_include([plugins/python-gi-imports-completion/configure.ac])
 m4_include([plugins/python-pack/configure.ac])
@@ -598,6 +599,7 @@ echo "  Make ................................. : ${enable_make_plugin}"
 echo "  Meson ................................ : ${enable_meson_plugin}"
 echo "  MinGW ................................ : ${enable_mingw_plugin}"
 echo "  Mono ................................. : ${enable_mono_plugin}"
+echo "  PHPize ............................... : ${enable_phpize_plugin}"
 echo "  Project Creation ..................... : ${enable_create_project_plugin}"
 echo "  Project Tree ......................... : ${enable_project_tree_plugin}"
 echo "  Python GObject Introspection ......... : ${enable_python_gi_imports_completion_plugin}"
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 40961e2..c6347b7 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -29,6 +29,7 @@ SUBDIRS =                            \
        meson-templates              \
        mingw                        \
        mono                         \
+       phpize                       \
        project-tree                 \
        python-gi-imports-completion \
        python-pack                  \
diff --git a/plugins/phpize/Makefile.am b/plugins/phpize/Makefile.am
new file mode 100644
index 0000000..e7426dd
--- /dev/null
+++ b/plugins/phpize/Makefile.am
@@ -0,0 +1,14 @@
+if ENABLE_PHPIZE_PLUGIN
+
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+dist_plugin_DATA = phpize.plugin
+
+moduledir = $(libdir)/gnome-builder/plugins
+dist_module_DATA = phpize_plugin.py
+
+endif
+
+-include $(top_srcdir)/git.mk
+
diff --git a/plugins/phpize/configure.ac b/plugins/phpize/configure.ac
new file mode 100644
index 0000000..de68438
--- /dev/null
+++ b/plugins/phpize/configure.ac
@@ -0,0 +1,12 @@
+# --enable-phpize-plugin=yes/no
+AC_ARG_ENABLE([phpize-plugin],
+              [AS_HELP_STRING([--enable-phpize-plugin=@<:@yes/no@:>@],
+                              [Build with support for the PHPize build system])],
+              [enable_phpize_plugin=$enableval],
+              [enable_phpize_plugin=yes])
+
+# for if ENABLE_PHPIZE_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_PHPIZE_PLUGIN, test x$enable_phpize_plugin = xyes)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/phpize/Makefile])
diff --git a/plugins/phpize/meson.build b/plugins/phpize/meson.build
new file mode 100644
index 0000000..a75e07a
--- /dev/null
+++ b/plugins/phpize/meson.build
@@ -0,0 +1,6 @@
+if get_option('with_phpize')
+
+install_data('phpize.plugin', install_dir: plugindir)
+install_data('phpize_plugin.py', install_dir: plugindir)
+
+endif
diff --git a/plugins/phpize/phpize.plugin b/plugins/phpize/phpize.plugin
new file mode 100644
index 0000000..f8982b2
--- /dev/null
+++ b/plugins/phpize/phpize.plugin
@@ -0,0 +1,11 @@
+[Plugin]
+Module=phpize_plugin
+Loader=python3
+Name=PHPize
+Description=Provides integration with phpize-based PHP extensions
+Authors=Christian Hergert <chergert redhat com>
+Copyright=Copyright © 2017 Christian Hergert
+Builtin=true
+Hidden=false
+X-Project-File-Filter-Pattern=config.m4
+X-Project-File-Filter-Name=PHPize Project (config.m4)
diff --git a/plugins/phpize/phpize_plugin.py b/plugins/phpize/phpize_plugin.py
new file mode 100644
index 0000000..230c392
--- /dev/null
+++ b/plugins/phpize/phpize_plugin.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python3
+
+#
+# phpize_plugin.py
+#
+# Copyright (C) 2017 Christian Hergert <chergert redhat com>
+#
+# 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/>.
+#
+
+import os
+import gi
+
+from gi.repository import Ide
+from gi.repository import GLib
+from gi.repository import GObject
+from gi.repository import Gio
+
+_TYPE_NONE = 0
+_TYPE_C = 1
+_TYPE_CPLUSPLUS = 2
+
+_BUILD_FLAGS_STDIN_BUF = """
+include Makefile
+
+print-%: ; @echo $* = $($*)
+"""
+
+def get_file_type(path):
+    suffix = path.split('.')[-1]
+    if suffix in ('c', 'h'):
+        return _TYPE_C
+    elif suffix in ('cpp', 'c++', 'cxx', 'cc',
+                    'hpp', 'h++', 'hxx', 'hh'):
+        return _TYPE_CPLUSPLUS
+    return _TYPE_NONE
+
+class PHPizeBuildSystem(Ide.Object, Ide.BuildSystem, Gio.AsyncInitable):
+    """
+    This is the the basis of the build system. It provides access to
+    some information about the project (like CFLAGS/CXXFLAGS, build targets,
+    etc). Of course, this skeleton doesn't do a whole lot right now.
+    """
+    project_file = GObject.Property(type=Gio.File)
+
+    def do_get_id(self):
+        return 'phpize'
+
+    def do_get_priority(self):
+        return 500
+
+    def do_init_async(self, priority, cancel, callback, data=None):
+        task = Gio.Task.new(self, cancel, callback)
+        task.set_priority(priority)
+
+        project_file = self.get_context().get_project_file()
+        if project_file.get_basename() == 'config.m4':
+            task.return_boolean(True)
+        else:
+            child = project_file.get_child('config.m4')
+            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_build_flags_async(self, ifile, cancellable, callback, data=None):
+        task = Gio.Task.new(self, cancellable, callback)
+        task.build_flags = []
+        task.type = get_file_type(ifile.get_path())
+
+        if not task.type:
+            task.return_boolean(True)
+            return
+
+        # To get the build flags, we run make with some custom code to
+        # print variables, and then extract the values based on the file type.
+        # But before, we must advance the pipeline through CONFIGURE.
+        build_manager = self.get_context().get_build_manager()
+        build_manager.execute_async(Ide.BuildPhase.CONFIGURE, None, self._get_build_flags_build_cb, task)
+
+    def do_get_build_flags_finish(self, result):
+        if result.propagate_boolean():
+            return result.build_flags
+
+    def _get_build_flags_build_cb(self, build_manager, result, task):
+        """
+        Completes the asynchronous call to advance the pipeline to CONFIGURE phase
+        and then runs a make subprocess to extract build flags from Makefile.
+        """
+        try:
+            build_manager.execute_finish(result)
+
+            pipeline = build_manager.get_pipeline()
+
+            # Launcher defaults to $builddir
+            launcher = pipeline.create_launcher()
+            launcher.set_flags(Gio.SubprocessFlags.STDIN_PIPE |
+                               Gio.SubprocessFlags.STDOUT_PIPE |
+                               Gio.SubprocessFlags.STDERR_PIPE)
+            launcher.push_argv('make')
+            launcher.push_argv('-f')
+            launcher.push_argv('-')
+            launcher.push_argv('print-CFLAGS')
+            launcher.push_argv('print-CXXFLAGS')
+            launcher.push_argv('print-INCLUDES')
+            subprocess = launcher.spawn()
+            subprocess.communicate_utf8_async(_BUILD_FLAGS_STDIN_BUF,
+                                              task.get_cancellable(),
+                                              self._get_build_flags_build_communicate_cb,
+                                              task)
+        except Exception as ex:
+            print(repr(ex))
+            task.return_error(GLib.Error(message=repr(ex)))
+
+    def _get_build_flags_build_communicate_cb(self, subprocess, result, task):
+        """
+        Completes the asynchronous request to get the build flags from the make
+        helper subprocess.
+        """
+        try:
+            _, stdout, stderr = subprocess.communicate_utf8_finish(result)
+
+            info = {}
+            for line in stdout.split('\n'):
+                if '=' in line:
+                    k,v = line.split('=', 1)
+                    info[k.strip()] = v.strip()
+
+            if task.type == _TYPE_C:
+                flags = info.get('CFLAGS', '') + " " + info.get('INCLUDES', '')
+            elif task.type == _TYPE_CPLUSPLUS:
+                flags = info.get('CXXFLAGS', '') + " " + info.get('INCLUDES', '')
+            else:
+                raise RuntimeError
+
+            _, build_flags = GLib.shell_parse_argv(flags)
+
+            task.build_flags = build_flags
+            task.return_boolean(True)
+
+        except Exception as ex:
+            print(repr(ex))
+            task.return_error(GLib.Error(message=repr(ex)))
+
+class PHPizeBuildSystemDiscovery(GObject.Object, Ide.BuildSystemDiscovery):
+    """
+    This is used to discover the build system based on the files within
+    the project. This can be useful if someone just clones the project and
+    we have to discover the build system dynamically (as opposed to opening
+    a particular build project file).
+    """
+
+    def do_discover(self, directory, cancellable):
+        try:
+            config_m4 = directory.get_child('config.m4')
+            if config_m4.query_exists():
+                stream = open(config_m4.get_path(), encoding='UTF-8')
+                if 'PHP_ARG_ENABLE' in stream.read():
+                    return ('phpize', 1000)
+        except Exception as ex:
+            print(repr(ex))
+
+        raise RuntimeError("Not a phpize build system")
+
+class PHPizeBuildPipelineAddin(Ide.Object, Ide.BuildPipelineAddin):
+    """
+    This class is responsible for attaching the various build operations
+    to the pipeline at the appropriate phase.
+
+    We need to bootstrap the project with phpize, and use make to build
+    the project.
+    """
+    def do_load(self, pipeline):
+        context = pipeline.get_context()
+        build_system = context.get_build_system()
+
+        if type(build_system) != PHPizeBuildSystem:
+            return
+
+        config = pipeline.get_configuration()
+        runtime = config.get_runtime()
+
+        srcdir = pipeline.get_srcdir()
+        builddir = pipeline.get_builddir()
+
+        # Bootstrap by calling phpize in the source directory
+        bootstrap_launcher = pipeline.create_launcher()
+        bootstrap_launcher.push_argv('phpize')
+        bootstrap_launcher.set_cwd(srcdir)
+        bootstrap_stage = Ide.BuildStageLauncher.new(context, bootstrap_launcher)
+        bootstrap_stage.set_completed(os.path.exists(os.path.join(srcdir, 'configure')))
+        self.track(pipeline.connect(Ide.BuildPhase.AUTOGEN, 0, bootstrap_stage))
+
+        # Configure the project using autoconf. We run from builddir.
+        config_launcher = pipeline.create_launcher()
+        config_launcher.set_flags(Gio.SubprocessFlags.STDIN_PIPE |
+                                  Gio.SubprocessFlags.STDOUT_PIPE |
+                                  Gio.SubprocessFlags.STDERR_PIPE)
+        config_launcher.push_argv(os.path.join(srcdir, 'configure'))
+        config_launcher.push_argv("--prefix={}".format(config.get_prefix()))
+        config_opts = config.get_config_opts()
+        if config_opts:
+            _, config_opts = GLib.shell_parse_argv(config_opts)
+            config_launcher.push_args(config_opts)
+        config_stage = Ide.BuildStageLauncher.new(context, config_launcher)
+        self.track(pipeline.connect(Ide.BuildPhase.CONFIGURE, 0, config_stage))
+
+        # Build the project using make.
+        build_launcher = pipeline.create_launcher()
+        build_launcher.push_argv('make')
+        if config.props.parallelism > 0:
+            build_launcher.push_argv('-j{}'.format(config.props.parallelism))
+        clean_launcher = pipeline.create_launcher()
+        clean_launcher.push_argv('make')
+        clean_launcher.push_argv('clean')
+        build_stage = Ide.BuildStageLauncher.new(context, build_launcher)
+        build_stage.set_clean_launcher(clean_launcher)
+        build_stage.connect('query', self._query)
+        self.track(pipeline.connect(Ide.BuildPhase.BUILD, 0, build_stage))
+
+        # Use "make install" to install the project.
+        install_launcher = pipeline.create_launcher()
+        install_launcher.push_argv('make')
+        install_launcher.push_argv('install')
+        install_stage = Ide.BuildStageLauncher.new(context, install_launcher)
+        self.track(pipeline.connect(Ide.BuildPhase.INSTALL, 0, install_stage))
+
+    def _query(self, stage, pipeline, cancellable):
+        # Always defer to make for completion status
+        stage.set_completed(False)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]