Commit d11be87e authored by Christian Hergert's avatar Christian Hergert
Browse files

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.
parent 5e871532
......@@ -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}"
......
......@@ -29,6 +29,7 @@ SUBDIRS = \
meson-templates \
mingw \
mono \
phpize \
project-tree \
python-gi-imports-completion \
python-pack \
......
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
# --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])
if get_option('with_phpize')
install_data('phpize.plugin', install_dir: plugindir)
install_data('phpize_plugin.py', install_dir: plugindir)
endif
[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)
#!/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)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment