anjuta r4507 - in trunk: . libanjuta libanjuta/interfaces plugins plugins/gbf-am plugins/gbf-am/GBF plugins/gbf-mkfile plugins/gbf-mkfile/GBF plugins/project-import plugins/project-manager
- From: sgranjoux svn gnome org
- To: svn-commits-list gnome org
- Subject: anjuta r4507 - in trunk: . libanjuta libanjuta/interfaces plugins plugins/gbf-am plugins/gbf-am/GBF plugins/gbf-mkfile plugins/gbf-mkfile/GBF plugins/project-import plugins/project-manager
- Date: Wed, 31 Dec 2008 11:55:52 +0000 (UTC)
Author: sgranjoux
Date: Wed Dec 31 11:55:52 2008
New Revision: 4507
URL: http://svn.gnome.org/viewvc/anjuta?rev=4507&view=rev
Log:
* configure.in
libanjuta/interfaces/libanjuta.idl
(added) libanjuta/gbf-project.c
(added) libanjuta/gbf-project.h
libanjuta/Makefile.am
plugins/Makefile.am
plugins/project-import/project-import.c
plugins/project-manager/plugin.c
plugins/project-manager/plugin.h
(added) plugins/project-manager/gbf-project-model.c
(added) plugins/project-manager/gbf-project-model.h
(added) plugins/project-manager/gbf-tree-data.c
(added) plugins/project-manager/gbf-tree-data.h
(added) plugins/project-manager/gbf-project-view.c
(added) plugins/project-manager/gbf-project-view.h
(added) plugins/project-manager/gbf-project-util.c
(added) plugins/project-manager/gbf-project-util.h
(added) plugins/project-manager/create_dialogs.glade
plugins/project-manager/Makefile.am
(added) plugins/gbf-am/plugin.c
(added) plugins/gbf-am/plugin.h
(added) plugins/gbf-am/gbf-am.plugin.in
(added) plugins/gbf-am/gbf-am-plugin-48.png
(added) plugins/gbf-am/gbf-am-config.h
(added) plugins/gbf-am/gbf-am-config.c
(added) plugins/gbf-am/gbf-am-project.h
(added) plugins/gbf-am/gbf-am-project.c
(added) plugins/gbf-am/gbf-am-properties.c
(added) plugins/gbf-am/gbf-am-properties.h
(added) plugins/gbf-am/gbf-am-parse.in
(added) plugins/gbf-am/gbf-am-dialogs.glade
(added) plugins/gbf-am/run-test.sh
(added) plugins/gbf-am/test.c
(added) plugins/gbf-am/output.dtd
(added) plugins/gbf-am/shared.xpm
(added) plugins/gbf-am/static.xpm
(added) plugins/gbf-am/unknown.xpm
(added) plugins/gbf-am/program.xpm
(added) plugins/gbf-am/GBF/General.pm
(added) plugins/gbf-am/GBF/AmFiles.pm
(added) plugins/gbf-am/GBF/.cvsignore
(added) plugins/gbf-am/GBF/Makefile.am
(added) plugins/gbf-am/.cvsignore
(added) plugins/gbf-am/Makefile.am
(added) plugins/gbf-mkfile/plugin.c
(added) plugins/gbf-mkfile/plugin.h
(added) plugins/gbf-mkfile/gbf-mkfile.plugin.in
(added) plugins/gbf-mkfile/gbf-mkfile-plugin-48.png
(added) plugins/gbf-mkfile/gbf-mkfile-properties.c
(added) plugins/gbf-mkfile/gbf-mkfile-properties.h
(added) plugins/gbf-mkfile/gbf-mkfile-config.c
(added) plugins/gbf-mkfile/gbf-mkfile-config.h
(added) plugins/gbf-mkfile/gbf-mkfile-project.c
(added) plugins/gbf-mkfile/gbf-mkfile-project.h
(added) plugins/gbf-mkfile/gbf-mkfile-parse.in
(added) plugins/gbf-mkfile/GBF/General.pm
(added) plugins/gbf-mkfile/GBF/Make.pm
(added) plugins/gbf-mkfile/GBF/.cvsignore
(added) plugins/gbf-mkfile/GBF/Makefile.am
(added) plugins/gbf-mkfile/.cvsignore
(added) plugins/gbf-mkfile/Makefile.am:
Move gnome-build library revision 643 in Anjuta tree
The file names have not changed
Added:
trunk/libanjuta/gbf-project.c (contents, props changed)
trunk/libanjuta/gbf-project.h (contents, props changed)
trunk/plugins/gbf-am/
trunk/plugins/gbf-am/.cvsignore
trunk/plugins/gbf-am/GBF/
trunk/plugins/gbf-am/GBF/.cvsignore
trunk/plugins/gbf-am/GBF/AmFiles.pm
trunk/plugins/gbf-am/GBF/General.pm
trunk/plugins/gbf-am/GBF/Makefile.am
trunk/plugins/gbf-am/Makefile.am
trunk/plugins/gbf-am/gbf-am-config.c (contents, props changed)
trunk/plugins/gbf-am/gbf-am-config.h (contents, props changed)
trunk/plugins/gbf-am/gbf-am-dialogs.glade
trunk/plugins/gbf-am/gbf-am-parse.in
trunk/plugins/gbf-am/gbf-am-plugin-48.png (contents, props changed)
trunk/plugins/gbf-am/gbf-am-project.c (contents, props changed)
trunk/plugins/gbf-am/gbf-am-project.h (contents, props changed)
trunk/plugins/gbf-am/gbf-am-properties.c (contents, props changed)
trunk/plugins/gbf-am/gbf-am-properties.h (contents, props changed)
trunk/plugins/gbf-am/gbf-am.plugin.in
trunk/plugins/gbf-am/output.dtd
trunk/plugins/gbf-am/plugin.c (contents, props changed)
trunk/plugins/gbf-am/plugin.h (contents, props changed)
trunk/plugins/gbf-am/program.xpm
trunk/plugins/gbf-am/run-test.sh (contents, props changed)
trunk/plugins/gbf-am/shared.xpm
trunk/plugins/gbf-am/static.xpm
trunk/plugins/gbf-am/test.c (contents, props changed)
trunk/plugins/gbf-am/unknown.xpm
trunk/plugins/gbf-mkfile/
trunk/plugins/gbf-mkfile/.cvsignore
trunk/plugins/gbf-mkfile/GBF/
trunk/plugins/gbf-mkfile/GBF/.cvsignore
trunk/plugins/gbf-mkfile/GBF/General.pm
trunk/plugins/gbf-mkfile/GBF/Make.pm
trunk/plugins/gbf-mkfile/GBF/Makefile.am
trunk/plugins/gbf-mkfile/Makefile.am
trunk/plugins/gbf-mkfile/gbf-mkfile-config.c (contents, props changed)
trunk/plugins/gbf-mkfile/gbf-mkfile-config.h (contents, props changed)
trunk/plugins/gbf-mkfile/gbf-mkfile-parse.in
trunk/plugins/gbf-mkfile/gbf-mkfile-plugin-48.png (contents, props changed)
trunk/plugins/gbf-mkfile/gbf-mkfile-project.c (contents, props changed)
trunk/plugins/gbf-mkfile/gbf-mkfile-project.h (contents, props changed)
trunk/plugins/gbf-mkfile/gbf-mkfile-properties.c (contents, props changed)
trunk/plugins/gbf-mkfile/gbf-mkfile-properties.h (contents, props changed)
trunk/plugins/gbf-mkfile/gbf-mkfile.plugin.in
trunk/plugins/gbf-mkfile/plugin.c (contents, props changed)
trunk/plugins/gbf-mkfile/plugin.h (contents, props changed)
trunk/plugins/project-manager/create_dialogs.glade
trunk/plugins/project-manager/gbf-project-model.c (contents, props changed)
trunk/plugins/project-manager/gbf-project-model.h (contents, props changed)
trunk/plugins/project-manager/gbf-project-util.c (contents, props changed)
trunk/plugins/project-manager/gbf-project-util.h (contents, props changed)
trunk/plugins/project-manager/gbf-project-view.c (contents, props changed)
trunk/plugins/project-manager/gbf-project-view.h (contents, props changed)
trunk/plugins/project-manager/gbf-tree-data.c (contents, props changed)
trunk/plugins/project-manager/gbf-tree-data.h (contents, props changed)
Modified:
trunk/ChangeLog
trunk/configure.in
trunk/libanjuta/Makefile.am
trunk/libanjuta/interfaces/libanjuta.idl
trunk/plugins/Makefile.am
trunk/plugins/project-import/project-import.c
trunk/plugins/project-manager/Makefile.am
trunk/plugins/project-manager/plugin.c
trunk/plugins/project-manager/plugin.h
Modified: trunk/configure.in
==============================================================================
--- trunk/configure.in (original)
+++ trunk/configure.in Wed Dec 31 11:55:52 2008
@@ -34,7 +34,6 @@
LIBXML_REQUIRED=2.4.23
LIBDEVHELP_REQUIRED=0.22
GDL_REQUIRED=0.7.3
-GNOMEBUILD_REQUIRED=0.3.0
GLADEUI_REQUIRED=3.2.0
LIBGRAPHVIZ_REQUIRED=1.0
NEON_REQUIRED=0.24.5
@@ -61,7 +60,6 @@
AC_SUBST(VTE_NEW_REQUIRED)
AC_SUBST(LIBDEVHELP_REQUIRED)
AC_SUBST(GDL_REQUIRED)
-AC_SUBST(GNOMEBUILD_REQUIRED)
AC_SUBST(GLADEUI_REQUIRED)
AC_SUBST(GLADEUI_SVN_REQUIRED)
AC_SUBST(LIBGRAPHVIZ_REQUIRED)
@@ -206,11 +204,6 @@
AM_CONDITIONAL(HAVE_PLUGIN_DEVHELP, [test x$devhelp_enabled = xyes])
-dnl Check for Gnome Build
-dnl ---------------------
-
-PKG_CHECK_MODULES(PLUGIN_GNOMEBUILD, gnome-build-1.0 >= $GNOMEBUILD_REQUIRED)
-
dnl Check for Glade3
dnl ---------------------
@@ -943,6 +936,14 @@
plugins/build-basic-autotools/Makefile
plugins/profiler/Makefile
plugins/project-manager/Makefile
+plugins/gbf-am/Makefile
+plugins/gbf-am/gbf-am-parse
+plugins/gbf-am/GBF/Makefile
+plugins/gbf-mkfile/Makefile
+plugins/gbf-mkfile/gbf-mkfile-parse
+plugins/gbf-mkfile/GBF/Makefile
+plugins/symbol-browser/Makefile
+plugins/symbol-browser/images/Makefile
plugins/symbol-db/images/Makefile
plugins/symbol-db/Makefile
plugins/symbol-db/test/Makefile
@@ -1023,6 +1024,8 @@
manuals/anjuta-build-tutorial/Makefile
mime/Makefile
])
+chmod +x ${ac_top_build_dir}plugins/gbf_am/gbf-am-parse
+chmod +x ${ac_top_build_dir}plugins/gbf_mkfile/gbf-mkfile-parse
echo " "
echo "-------------------------------------------------------------------"
echo "Conditionally built plugins:"
Modified: trunk/libanjuta/Makefile.am
==============================================================================
--- trunk/libanjuta/Makefile.am (original)
+++ trunk/libanjuta/Makefile.am Wed Dec 31 11:55:52 2008
@@ -75,7 +75,9 @@
anjuta-async-notify.c \
anjuta-message-area.h \
anjuta-debug.c \
- anjuta-debug.h
+ anjuta-debug.h \
+ gbf-project.c \
+ gbf-project.h
if HAVE_PLUGIN_GLADE
Added: trunk/libanjuta/gbf-project.c
==============================================================================
--- (empty file)
+++ trunk/libanjuta/gbf-project.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,568 @@
+/* gbf-project.c
+ *
+ * Copyright (C) 2002 Jeroen Zwartepoorte
+ *
+ * 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 2 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libgnome/gnome-macros.h>
+#include "gbf-project.h"
+
+GNOME_CLASS_BOILERPLATE (GbfProject, gbf_project, GObject, G_TYPE_OBJECT);
+
+void
+gbf_project_load (GbfProject *project,
+ const gchar *path,
+ GError **error)
+{
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (GBF_IS_PROJECT (project));
+ g_return_if_fail (path != NULL);
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ GBF_PROJECT_GET_CLASS (project)->load (project, path, error);
+}
+
+gboolean
+gbf_project_probe (GbfProject *project,
+ const gchar *path,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, FALSE);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), FALSE);
+ g_return_val_if_fail (path != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return GBF_PROJECT_GET_CLASS (project)->probe (project, path, error);
+}
+
+void
+gbf_project_refresh (GbfProject *project,
+ GError **error)
+{
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (GBF_IS_PROJECT (project));
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ GBF_PROJECT_GET_CLASS (project)->refresh (project, error);
+}
+
+GbfProjectCapabilities
+gbf_project_get_capabilities (GbfProject *project,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, GBF_PROJECT_CAN_ADD_NONE);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), GBF_PROJECT_CAN_ADD_NONE);
+ g_return_val_if_fail (error == NULL || *error == NULL, GBF_PROJECT_CAN_ADD_NONE);
+
+ return GBF_PROJECT_GET_CLASS (project)->get_capabilities (project, error);
+}
+
+/* Groups. */
+gchar *
+gbf_project_add_group (GbfProject *project,
+ const gchar *parent_id,
+ const gchar *name,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (parent_id != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->add_group (project,
+ parent_id,
+ name,
+ error);
+}
+
+void
+gbf_project_remove_group (GbfProject *project,
+ const gchar *id,
+ GError **error)
+{
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (GBF_IS_PROJECT (project));
+ g_return_if_fail (id != NULL);
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ GBF_PROJECT_GET_CLASS (project)->remove_group (project, id, error);
+}
+
+GbfProjectGroup *
+gbf_project_get_group (GbfProject *project,
+ const gchar *id,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->get_group (project, id, error);
+}
+
+GtkWidget *
+gbf_project_configure_group (GbfProject *project,
+ const gchar *id,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->configure_group (project,
+ id,
+ error);
+}
+
+GtkWidget *
+gbf_project_configure_new_group (GbfProject *project,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->configure_new_group (project,
+ error);
+}
+
+/* Targets. */
+gchar *
+gbf_project_add_target (GbfProject *project,
+ const gchar *group_id,
+ const gchar *name,
+ const gchar *type,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (group_id != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (type != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->add_target (project,
+ group_id,
+ name,
+ type,
+ error);
+}
+
+void
+gbf_project_remove_target (GbfProject *project,
+ const gchar *id,
+ GError **error)
+{
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (GBF_IS_PROJECT (project));
+ g_return_if_fail (id != NULL);
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ GBF_PROJECT_GET_CLASS (project)->remove_target (project, id, error);
+}
+
+GbfProjectTarget *
+gbf_project_get_target (GbfProject *project,
+ const gchar *id,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->get_target (project,
+ id,
+ error);
+}
+
+GList *
+gbf_project_get_all_targets (GbfProject *project,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->get_all_targets (project,
+ error);
+}
+
+GList *
+gbf_project_get_all_groups (GbfProject *project,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->get_all_groups (project,
+ error);
+}
+
+GtkWidget *
+gbf_project_configure_target (GbfProject *project,
+ const gchar *id,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->configure_target (project,
+ id,
+ error);
+}
+GtkWidget *
+gbf_project_configure_new_target (GbfProject *project,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->configure_new_target (project,
+ error);
+}
+
+/* Sources. */
+gchar *
+gbf_project_add_source (GbfProject *project,
+ const gchar *target_id,
+ const gchar *uri,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (target_id != NULL, NULL);
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->add_source (project,
+ target_id,
+ uri,
+ error);
+}
+
+void
+gbf_project_remove_source (GbfProject *project,
+ const gchar *id,
+ GError **error)
+{
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (GBF_IS_PROJECT (project));
+ g_return_if_fail (id != NULL);
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ GBF_PROJECT_GET_CLASS (project)->remove_source (project, id, error);
+}
+
+GbfProjectTargetSource *
+gbf_project_get_source (GbfProject *project,
+ const gchar *id,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->get_source (project,
+ id,
+ error);
+}
+
+GList *
+gbf_project_get_all_sources (GbfProject *project,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->get_all_sources (project,
+ error);
+}
+
+GtkWidget *
+gbf_project_configure_source (GbfProject *project,
+ const gchar *id,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->configure_source (project,
+ id,
+ error);
+}
+
+GtkWidget *
+gbf_project_configure_new_source (GbfProject *project,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->configure_new_source (project,
+ error);
+}
+
+/* Project */
+
+GtkWidget *
+gbf_project_configure (GbfProject *project, GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->configure (project, error);
+}
+
+GList *
+gbf_project_get_config_modules (GbfProject *project, GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->get_config_modules (project, error);
+}
+
+GList *
+gbf_project_get_config_packages (GbfProject *project,
+ const gchar* module,
+ GError **error)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (module != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->get_config_packages (project, module,
+ error);
+}
+
+/* Types. */
+const gchar *
+gbf_project_name_for_type (GbfProject *project,
+ const gchar *type)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (type != NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->name_for_type (project, type);
+}
+
+const gchar *
+gbf_project_mimetype_for_type (GbfProject *project,
+ const gchar *type)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (type != NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->mimetype_for_type (project, type);
+}
+
+gchar **
+gbf_project_get_types (GbfProject *project)
+{
+ g_return_val_if_fail (project != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT (project), NULL);
+ g_return_val_if_fail (
+ GBF_PROJECT_GET_CLASS (project)->get_types != NULL, NULL);
+
+ return GBF_PROJECT_GET_CLASS (project)->get_types (project);
+}
+
+static void
+gbf_project_class_init (GbfProjectClass *klass)
+{
+ parent_class = g_type_class_peek_parent (klass);
+
+ g_signal_new ("project-updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GbfProjectClass, project_updated),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+gbf_project_instance_init (GbfProject *project)
+{
+}
+
+GQuark
+gbf_project_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (quark == 0)
+ quark = g_quark_from_string ("gbf-project-quark");
+
+ return quark;
+}
+
+GbfProjectGroup *
+gbf_project_group_copy (GbfProjectGroup *group)
+{
+ GbfProjectGroup *new_group;
+ GList *l;
+
+ new_group = g_new0 (GbfProjectGroup, 1);
+ new_group->id = g_strdup (group->id);
+ new_group->parent_id = g_strdup (group->parent_id);
+ new_group->name = g_strdup (group->name);
+
+ for (l = group->groups; l; l = l->next)
+ new_group->groups = g_list_prepend (new_group->groups, g_strdup (l->data));
+ new_group->groups = g_list_reverse (new_group->groups);
+
+ for (l = group->targets; l; l = l->next)
+ new_group->targets = g_list_prepend (new_group->targets, g_strdup (l->data));
+ new_group->targets = g_list_reverse (new_group->targets);
+
+ return new_group;
+}
+
+void
+gbf_project_group_free (GbfProjectGroup *group)
+{
+ g_free (group->id);
+ g_free (group->parent_id);
+ g_free (group->name);
+
+ while (group->groups) {
+ g_free (group->groups->data);
+ group->groups = g_list_delete_link (group->groups, group->groups);
+ }
+
+ while (group->targets) {
+ g_free (group->targets->data);
+ group->targets = g_list_delete_link (group->targets, group->targets);
+ }
+ g_free (group);
+}
+
+GbfProjectTarget *
+gbf_project_target_copy (GbfProjectTarget *target)
+{
+ GbfProjectTarget *new_target;
+ GList *l;
+
+ new_target = g_new0 (GbfProjectTarget, 1);
+ new_target->id = g_strdup (target->id);
+ new_target->group_id = g_strdup (target->group_id);
+ new_target->name = g_strdup (target->name);
+ new_target->type = g_strdup (target->type);
+
+ for (l = target->sources; l; l = l->next)
+ new_target->sources = g_list_prepend (new_target->sources, g_strdup (l->data));
+ new_target->sources = g_list_reverse (new_target->sources);
+
+ return new_target;
+}
+
+void
+gbf_project_target_free (GbfProjectTarget *target)
+{
+ g_free (target->id);
+ g_free (target->group_id);
+ g_free (target->name);
+ g_free (target->type);
+
+ while (target->sources) {
+ g_free (target->sources->data);
+ target->sources = g_list_delete_link (target->sources, target->sources);
+ }
+
+ g_free (target);
+}
+
+GbfProjectTargetSource *
+gbf_project_target_source_copy (GbfProjectTargetSource *source)
+{
+ GbfProjectTargetSource *new_source;
+
+ new_source = g_new0 (GbfProjectTargetSource, 1);
+ new_source->id = g_strdup (source->id);
+ new_source->target_id = g_strdup (source->target_id);
+ new_source->source_uri = g_strdup (source->source_uri);
+
+ return new_source;
+}
+
+void
+gbf_project_target_source_free (GbfProjectTargetSource *source)
+{
+ g_free (source->id);
+ g_free (source->target_id);
+ g_free (source->source_uri);
+ g_free (source);
+}
+
+GType
+gbf_project_group_get_type (void)
+{
+ static GType our_type = 0;
+
+ if (our_type == 0)
+ our_type = g_boxed_type_register_static ("GbfProjectGroup",
+ (GBoxedCopyFunc) gbf_project_group_copy,
+ (GBoxedFreeFunc) gbf_project_group_free);
+
+ return our_type;
+}
+
+GType
+gbf_project_target_get_type (void)
+{
+ static GType our_type = 0;
+
+ if (our_type == 0)
+ our_type = g_boxed_type_register_static ("GbfProjectTarget",
+ (GBoxedCopyFunc) gbf_project_target_copy,
+ (GBoxedFreeFunc) gbf_project_target_free);
+
+ return our_type;
+}
+
+GType
+gbf_project_target_source_get_type (void)
+{
+ static GType our_type = 0;
+
+ if (our_type == 0)
+ our_type = g_boxed_type_register_static ("GbfProjectTargetSource",
+ (GBoxedCopyFunc) gbf_project_target_source_copy,
+ (GBoxedFreeFunc) gbf_project_target_source_free);
+
+ return our_type;
+}
Added: trunk/libanjuta/gbf-project.h
==============================================================================
--- (empty file)
+++ trunk/libanjuta/gbf-project.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,337 @@
+/* gbf-project.h
+ *
+ * Copyright (C) 2002 Jeroen Zwartepoorte
+ *
+ * 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 2 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _GBF_PROJECT_H_
+#define _GBF_PROJECT_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GBF_TYPE_PROJECT (gbf_project_get_type ())
+#define GBF_PROJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GBF_TYPE_PROJECT, GbfProject))
+#define GBF_PROJECT_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST ((klass), GBF_TYPE_PROJECT, GbfProjectClass))
+#define GBF_IS_PROJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GBF_TYPE_PROJECT))
+#define GBF_IS_PROJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GBF_TYPE_PROJECT))
+#define GBF_PROJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GBF_TYPE_PROJECT, GbfProjectClass))
+
+#define GBF_PROJECT_ERROR (gbf_project_error_quark ())
+
+#define GBF_TYPE_PROJECT_GROUP (gbf_project_group_get_type ())
+#define GBF_TYPE_PROJECT_TARGET (gbf_project_target_get_type ())
+#define GBF_TYPE_PROJECT_TARGET_SOURCE (gbf_project_target_source_get_type ())
+
+#define GBF_BUILD_ID_DEFAULT "DEFAULT"
+
+typedef struct _GbfProject GbfProject;
+typedef struct _GbfProjectClass GbfProjectClass;
+typedef struct _GbfProjectGroup GbfProjectGroup;
+typedef struct _GbfProjectTarget GbfProjectTarget;
+typedef struct _GbfProjectTargetSource GbfProjectTargetSource;
+typedef enum _GbfProjectError GbfProjectError;
+typedef enum _GbfProjectCapabilities GbfProjectCapabilities;
+
+struct _GbfProjectGroup {
+ gchar *id;
+ gchar *parent_id;
+
+ gchar *name;
+
+ GList *groups;
+ GList *targets;
+};
+
+struct _GbfProjectTarget {
+ gchar *id;
+ gchar *group_id;
+
+ gchar *name;
+ gchar *type;
+
+ GList *sources;
+};
+
+struct _GbfProjectTargetSource {
+ gchar *id;
+ gchar *target_id;
+
+ gchar *source_uri;
+};
+
+/* FIXME: extend this list */
+enum _GbfProjectError {
+ GBF_PROJECT_ERROR_SUCCESS = 0,
+ GBF_PROJECT_ERROR_DOESNT_EXIST,
+ GBF_PROJECT_ERROR_ALREADY_EXISTS,
+ GBF_PROJECT_ERROR_VALIDATION_FAILED,
+ GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ GBF_PROJECT_ERROR_GENERAL_FAILURE,
+};
+
+enum _GbfProjectCapabilities {
+ GBF_PROJECT_CAN_ADD_NONE = 0,
+ GBF_PROJECT_CAN_ADD_GROUP = 1 << 0,
+ GBF_PROJECT_CAN_ADD_TARGET = 1 << 1,
+ GBF_PROJECT_CAN_ADD_SOURCE = 1 << 2,
+ GBF_PROJECT_CAN_PACKAGES = 1 << 3
+};
+
+struct _GbfProject {
+ GObject parent;
+};
+
+struct _GbfProjectClass {
+ GObjectClass parent_class;
+
+ void (* project_updated) (GbfProject *project);
+
+ /* Virtual Table */
+ /* Project. */
+ void (* load) (GbfProject *project,
+ const gchar *path,
+ GError **error);
+ gboolean (* probe) (GbfProject *project,
+ const gchar *path,
+ GError **error);
+ void (* refresh) (GbfProject *project,
+ GError **error);
+ GbfProjectCapabilities (* get_capabilities) (GbfProject *project,
+ GError **error);
+
+ /* Groups. */
+ gchar * (* add_group) (GbfProject *project,
+ const gchar *parent_id,
+ const gchar *name,
+ GError **error);
+ void (* remove_group) (GbfProject *project,
+ const gchar *id,
+ GError **error);
+ GbfProjectGroup * (* get_group) (GbfProject *project,
+ const gchar *id,
+ GError **error);
+ GList * (* get_all_groups) (GbfProject *project,
+ GError **error);
+ GtkWidget * (* configure_group) (GbfProject *project,
+ const gchar *id,
+ GError **error);
+ GtkWidget * (* configure_new_group) (GbfProject *project,
+ GError **error);
+
+ /* Targets. */
+ gchar * (* add_target) (GbfProject *project,
+ const gchar *group_id,
+ const gchar *name,
+ const gchar *type,
+ GError **error);
+ void (* remove_target) (GbfProject *project,
+ const gchar *id,
+ GError **error);
+ GbfProjectTarget * (* get_target) (GbfProject *project,
+ const gchar *id,
+ GError **error);
+ GList * (* get_all_targets) (GbfProject *project,
+ GError **error);
+ GtkWidget * (* configure_target) (GbfProject *project,
+ const gchar *id,
+ GError **error);
+ GtkWidget * (* configure_new_target) (GbfProject *project,
+ GError **error);
+
+ /* Sources. */
+ gchar * (* add_source) (GbfProject *project,
+ const gchar *target_id,
+ const gchar *uri,
+ GError **error);
+ void (* remove_source) (GbfProject *project,
+ const gchar *id,
+ GError **error);
+ GbfProjectTargetSource * (* get_source) (GbfProject *project,
+ const gchar *id,
+ GError **error);
+ GList * (* get_all_sources) (GbfProject *project,
+ GError **error);
+ GtkWidget * (* configure_source) (GbfProject *project,
+ const gchar *id,
+ GError **error);
+ GtkWidget * (* configure_new_source) (GbfProject *project,
+ GError **error);
+ GtkWidget * (* configure) (GbfProject *project,
+ GError **error);
+
+ GList * (* get_config_modules) (GbfProject *project,
+ GError **error);
+ GList * (* get_config_packages) (GbfProject *project,
+ const gchar* module,
+ GError **error);
+
+
+ /* Types. */
+ const gchar * (* name_for_type) (GbfProject *project,
+ const gchar *type);
+ const gchar * (* mimetype_for_type) (GbfProject *project,
+ const gchar *type);
+ gchar ** (* get_types) (GbfProject *project);
+};
+
+GQuark gbf_project_error_quark (void);
+GType gbf_project_get_type (void);
+GType gbf_project_group_get_type (void);
+GType gbf_project_target_get_type (void);
+GType gbf_project_target_source_get_type (void);
+void gbf_project_load (GbfProject *project,
+ const gchar *path,
+ GError **error);
+gboolean gbf_project_probe (GbfProject *project,
+ const gchar *path,
+ GError **error);
+void gbf_project_refresh (GbfProject *project,
+ GError **error);
+GbfProjectCapabilities gbf_project_get_capabilities (GbfProject *project,
+ GError **error);
+
+/* Groups. */
+gchar *gbf_project_add_group (GbfProject *project,
+ const gchar *parent_id,
+ const gchar *name,
+ GError **error);
+void gbf_project_remove_group (GbfProject *project,
+ const gchar *id,
+ GError **error);
+GbfProjectGroup *gbf_project_get_group (GbfProject *project,
+ const gchar *id,
+ GError **error);
+GList *gbf_project_get_all_groups (GbfProject *project,
+ GError **error);
+GtkWidget *gbf_project_configure_group (GbfProject *project,
+ const gchar *id,
+ GError **error);
+GtkWidget *gbf_project_configure_new_group (GbfProject *project,
+ GError **error);
+
+
+/* Targets. */
+gchar *gbf_project_add_target (GbfProject *project,
+ const gchar *group_id,
+ const gchar *name,
+ const gchar *type,
+ GError **error);
+void gbf_project_remove_target (GbfProject *project,
+ const gchar *id,
+ GError **error);
+GbfProjectTarget *gbf_project_get_target (GbfProject *project,
+ const gchar *id,
+ GError **error);
+GList *gbf_project_get_all_targets (GbfProject *project,
+ GError **error);
+GtkWidget *gbf_project_configure_target (GbfProject *project,
+ const gchar *id,
+ GError **error);
+GtkWidget *gbf_project_configure_new_target (GbfProject *project,
+ GError **error);
+
+
+/* Sources. */
+gchar *gbf_project_add_source (GbfProject *project,
+ const gchar *target_id,
+ const gchar *uri,
+ GError **error);
+void gbf_project_remove_source (GbfProject *project,
+ const gchar *id,
+ GError **error);
+GbfProjectTargetSource *gbf_project_get_source (GbfProject *project,
+ const gchar *id,
+ GError **error);
+GList *gbf_project_get_all_sources (GbfProject *project,
+ GError **error);
+GtkWidget *gbf_project_configure_source (GbfProject *project,
+ const gchar *id,
+ GError **error);
+GtkWidget *gbf_project_configure_new_source (GbfProject *project,
+ GError **error);
+/* Project */
+
+GtkWidget *gbf_project_configure (GbfProject *project,
+ GError **error);
+
+/* Packages */
+GList *gbf_project_get_config_modules (GbfProject *project,
+ GError** error);
+
+GList *gbf_project_get_config_packages (GbfProject *project,
+ const gchar* module,
+ GError** error);
+
+
+/* Types. */
+const gchar *gbf_project_name_for_type (GbfProject *project,
+ const gchar *type);
+const gchar *gbf_project_mimetype_for_type (GbfProject *project,
+ const gchar *type);
+
+gchar **gbf_project_get_types (GbfProject *project);
+
+/* functions for copying/freeing data structures */
+
+GbfProjectGroup *gbf_project_group_copy (GbfProjectGroup *group);
+void gbf_project_group_free (GbfProjectGroup *group);
+
+GbfProjectTarget *gbf_project_target_copy (GbfProjectTarget *target);
+void gbf_project_target_free (GbfProjectTarget *target);
+
+GbfProjectTargetSource *gbf_project_target_source_copy (GbfProjectTargetSource *source);
+void gbf_project_target_source_free (GbfProjectTargetSource *source);
+
+
+
+
+#define GBF_BACKEND_BOILERPLATE(class_name, prefix) \
+GType \
+prefix##_get_type (GTypeModule *module) \
+{ \
+ static GType type = 0; \
+ if (!type) { \
+ static const GTypeInfo type_info = { \
+ sizeof (class_name##Class), \
+ NULL, \
+ NULL, \
+ (GClassInitFunc)prefix##_class_init, \
+ NULL, \
+ NULL, \
+ sizeof (class_name), \
+ 0, \
+ (GInstanceInitFunc)prefix##_instance_init \
+ }; \
+ if (module == NULL) { \
+ type = g_type_register_static (GBF_TYPE_PROJECT, \
+ #class_name, \
+ &type_info, 0); \
+ } else { \
+ type = g_type_module_register_type (module, \
+ GBF_TYPE_PROJECT, \
+ #class_name, \
+ &type_info, 0); \
+ } \
+ } \
+ return type; \
+}
+
+G_END_DECLS
+
+#endif /* _GBF_PROJECT_H_ */
Modified: trunk/libanjuta/interfaces/libanjuta.idl
==============================================================================
--- trunk/libanjuta/interfaces/libanjuta.idl (original)
+++ trunk/libanjuta/interfaces/libanjuta.idl Wed Dec 31 11:55:52 2008
@@ -2868,6 +2868,31 @@
}
/**
+ * SECTION:ianjuta-project-backend
+ * @title: IAnjutaProjectBackend
+ * @short_description: Interface for creating new project
+ * @see_also:
+ * @stability: Unstable
+ * @include: libanjuta/interfaces/ianjuta-project-backend.h
+ *
+ */
+interface IAnjutaProjectBackend
+{
+ #include <libanjuta/gbf-project.h>
+
+ /**
+ * ianjuta_project_backend_new_project:
+ * @obj: Self
+ * @err: Error propagation and reporting
+ *
+ * Get a new GbfProject
+ *
+ * Return value: An object derived from GbfProject
+ */
+ GbfProject* new_project ();
+}
+
+/**
* SECTION:ianjuta-project-manager
* @title: IAnjutaProjectManager
* @short_description: Interface for project managers
Modified: trunk/plugins/Makefile.am
==============================================================================
--- trunk/plugins/Makefile.am (original)
+++ trunk/plugins/Makefile.am Wed Dec 31 11:55:52 2008
@@ -35,6 +35,8 @@
sourceview \
run-program \
scratchbox \
+ gbf-am \
+ gbf-mkfile \
starter
# indent
Added: trunk/plugins/gbf-am/.cvsignore
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/.cvsignore Wed Dec 31 11:55:52 2008
@@ -0,0 +1,6 @@
+Makefile.in
+Makefile
+.deps
+.libs
+test
+gbf-am-parse
Added: trunk/plugins/gbf-am/GBF/.cvsignore
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/GBF/.cvsignore Wed Dec 31 11:55:52 2008
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Added: trunk/plugins/gbf-am/GBF/AmFiles.pm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/GBF/AmFiles.pm Wed Dec 31 11:55:52 2008
@@ -0,0 +1,1647 @@
+package GBF::AmFiles;
+
+use Exporter;
+use strict;
+use Fcntl ":seek";
+
+our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+ ISA = qw (Exporter);
+
+ EXPORT = qw (&debug &enable_debug
+ &parse_am_file &output_am_file
+ &parse_configure_in &parse_configure_in_buffer
+ &output_configure_in
+ &configure_rewrite_variable &configure_remove_variable
+ &configure_rewrite_packages
+ &configure_rewrite_package_info
+ &use_macro &use_rule &expand_one_var
+ ¯o_create ¯o_remove ¯o_rewrite
+ ¯o_append_text ¯o_prepend_text ¯o_remove_text
+ &rule_remove &am_file_reset
+ &edit_config_files);
+ EXPORT_OK = qw (&test_print_am_file &get_line_from_hint &get_config_files);
+%EXPORT_TAGS = (DEBUG => [qw (&get_config_files &test_print_am_file)]);
+
+
+use GBF::General;
+
+
+my $debug = 0;
+
+sub debug { my $message = $_[0]; print STDERR "DEBUG: $message\n" if $debug; }
+sub enable_debug { $debug = 1; }
+
+# +----------------
+# | makefile : information extracted from the parsing of Makefile.am
+# +-----------------
+# | filename : full path to Makefile.am file
+# | lines : list of lines of text of the file
+# | macros : hash of macro hashes (defined below)
+# | (macro_name => macro_hash)
+# | rules : hash of rule hashes (defined below) (rule_name => rule_hash)
+# | extra : list of other lines of text (i.e. not rules neither macros)
+# | dirty : whether the makefile needs flushing
+# |
+#
+# +----------------
+# | macro : a Makefile.am macro definition
+# +-----------------
+# | contents : string with the contents of the macro
+# | atline : line number at the makefile.am where the macro is
+# | first defined
+# | used : flag indicating whether the macro has already been used
+# | in target extraction
+# |
+#
+# +----------------
+# | rule : a Makefile.am rule definition
+# +-----------------
+# | dependencies : string containing the dependencies of the rule
+# | actions : string containing the rule actions
+# | atline : line number at the makefile.am where the rule is defined
+# | used : flag indicating whether the rule has already been used
+# | in target extraction
+# |
+#
+
+######################################################################
+# Constants and Variables
+######################################################################
+
+# String constants.
+# These are stolen from automake
+
+my $IGNORE_PATTERN = '^\s*##([^#\n].*)?\n';
+my $WHITE_PATTERN = '^\s*' . "\$";
+my $COMMENT_PATTERN = '^#';
+my $TARGET_PATTERN='[$a-zA-Z_ %][- a-zA-Z0-9_(){}/$+ %]*';
+# A rule has three parts: a list of targets, a list of dependencies,
+# and optionally actions.
+my $RULE_PATTERN =
+ "^($TARGET_PATTERN(?:(?:\\\\\n|\\s)+$TARGET_PATTERN)*) *:([^=].*|)\$";
+
+# Only recognize leading spaces, not leading tabs. If we recognize
+# leading tabs here then we need to make the reader smarter, because
+# otherwise it will think rules like `foo=bar; \' are errors.
+my $ASSIGNMENT_PATTERN = '^ *([^ \t=:+]*)\s*([:+]?)=\s*(.*)' . "\$";
+# This pattern recognizes a Gnits version id and sets $1 if the
+# release is an alpha release. We also allow a suffix which can be
+# used to extend the version number with a "fork" identifier.
+my $GNITS_VERSION_PATTERN = '\d+\.\d+([a-z]|\.\d+)?(-[A-Za-z0-9]+)?';
+
+my $IF_PATTERN = '^if\s+(!?)\s*([A-Za-z][A-Za-z0-9_]*)\s*(?:#.*)?' . "\$";
+my $ELSE_PATTERN =
+ '^else(?:\s+(!?)\s*([A-Za-z][A-Za-z0-9_]*))?\s*(?:#.*)?' . "\$";
+my $ENDIF_PATTERN =
+ '^endif(?:\s+(!?)\s*([A-Za-z][A-Za-z0-9_]*))?\s*(?:#.*)?' . "\$";
+my $PATH_PATTERN = '(\w|[+/.-])+';
+# This will pass through anything not of the prescribed form.
+my $INCLUDE_PATTERN = ('^include\s+'
+ . '((\$\(top_srcdir\)/' . $PATH_PATTERN . ')'
+ . '|(\$\(srcdir\)/' . $PATH_PATTERN . ')'
+ . '|([^/\$]' . $PATH_PATTERN . '))\s*(#.*)?' . "\$");
+
+# Match `-d' as a command-line argument in a string.
+my $DASH_D_PATTERN = "(^|\\s)-d(\\s|\$)";
+# Directories installed during 'install-exec' phase.
+my $EXEC_DIR_PATTERN =
+ '^(?:bin|sbin|libexec|sysconf|localstate|lib|pkglib|.*exec.*)' . "\$";
+
+my %default_macro_style = ( bs_column => -1,
+ items_per_line => 1,
+ first_line_ignored => 1,
+ leading_space => "\t" );
+
+
+######################################################################
+# Parser Functions
+######################################################################
+
+# &check_trailing_slash ( $LINE)
+# --------------------------------------
+# Return 1 iff $LINE ends with a slash.
+# Might modify $LINE.
+sub check_trailing_slash (\$)
+{
+ my ($line) = @_;
+
+ # Ignore `##' lines.
+ return 0 if $$line =~ /$IGNORE_PATTERN/o;
+
+ # Catch and fix a common error.
+ #msg "syntax", $where, "whitespace following trailing backslash"
+ $$line =~ s/\\\s+\n$/\\\n/;
+
+ return $$line =~ /\\$/;
+}
+
+# Function parse_file
+
+# Parses a Makefile.am file extracting the macros and rules in it, while saving
+# its contents to an list of lines.
+
+# FIXME: handle includes and conditionals
+
+sub parse_am_file
+{
+ my $filename = $_[0];
+
+ my (%macros, %rules, @extra_text);
+
+ if (! open (MAKEFILE, "< $filename")) {
+ &report_error (100, "Can't open file $filename: $!");
+ return {};
+ }
+
+
+ use constant IN_VAR_DEF => 0;
+ use constant IN_RULE_DEF => 1;
+ use constant IN_COMMENT => 2;
+ my $prev_state = IN_RULE_DEF;
+ my $saw_bk = 0;
+ my $current_rule = "";
+ my @lines = ();
+ my $rule_indentation = "";
+
+ while (<MAKEFILE>) {
+ push @lines, $_;
+
+ if (/$IGNORE_PATTERN/o) {
+ # Merely delete comments beginning with two hashes.
+
+ } elsif (/$WHITE_PATTERN/o) {
+ if ($saw_bk) {
+ &report_error (101, "$filename:$.: blank line following " .
+ "trailing backslash");
+ return {};
+ };
+
+ } elsif (/$COMMENT_PATTERN/o) {
+ $prev_state = IN_COMMENT;
+ } else {
+ last;
+ }
+ $saw_bk = check_trailing_slash ($_);
+ }
+
+ my $last_var_name = '';
+ my $last_var_type = '';
+ my $last_var_value = '';
+ # FIXME: shouldn't use $_ in this loop; it is too big.
+ while ($_) {
+ # Make sure the line is \n-terminated.
+ chomp;
+ $_ .= "\n";
+
+ my $new_saw_bk = check_trailing_slash ($_);
+
+ if (/$IGNORE_PATTERN/o) {
+ # Merely delete comments beginning with two hashes.
+
+ # Keep any backslash from the previous line.
+ $new_saw_bk = $saw_bk;
+ } elsif (/$WHITE_PATTERN/o) {
+ if ($saw_bk) {
+ &report_error (101, "$filename:$.: blank line following " .
+ "trailing backslash");
+ return {};
+ };
+
+ } elsif (/$COMMENT_PATTERN/o) {
+ if ($saw_bk && $prev_state != IN_COMMENT) {
+ &report_error (101, "$filename:$.: comment following " .
+ "trailing backslash");
+ return {};
+ };
+ $prev_state = IN_COMMENT;
+ } elsif ($saw_bk) {
+ if ($prev_state == IN_RULE_DEF) {
+ if ($rules{$current_rule}{actions} eq "") {
+ # Strip trailing backslash from rule deps
+ s/\\\s*$//;
+ $rules{$current_rule}{dependencies} .= $_;
+ } else {
+ $rules{$current_rule}{actions} .= $_;
+ };
+ } elsif ($prev_state == IN_COMMENT) {
+ } else # $prev_state == IN_VAR_DEF
+ {
+ # Chop newline and backslash if this line is
+ # continued. ensure trailing whitespace exists.
+ s/\\\s*$//;
+ my $var_value = '';
+ $var_value = ' '
+ unless $last_var_value =~ /\s$/;
+ $var_value .= $_;
+ $macros{$last_var_name}{contents} .= $var_value;
+ $last_var_value .= $var_value;
+ }
+ } elsif (/$IF_PATTERN/o) {
+ #$cond = cond_stack_if ($1, $2, $where);
+ } elsif (/$ELSE_PATTERN/o) {
+ #$cond = cond_stack_else ($1, $2, $where);
+ } elsif (/$ENDIF_PATTERN/o) {
+ #$cond = cond_stack_endif ($1, $2, $where);
+ } elsif (/$RULE_PATTERN/o) {
+ # Found a rule.
+ $current_rule = $1;
+ &debug ("$filename:$.: Found rule '$current_rule'");
+ $prev_state = IN_RULE_DEF;
+ $rule_indentation = "";
+
+ my $value = $2;
+ if (defined ($value)) {
+ $value =~ s/\\\s*$//;
+ } else {
+ $value = "";
+ };
+ $rules{$current_rule} = { actions => "",
+ dependencies => $value,
+ atline => $.,
+ used => 0 };
+ } elsif (/$ASSIGNMENT_PATTERN/o) {
+ # Found a macro definition.
+ $prev_state = IN_VAR_DEF;
+ $last_var_name = $1;
+ $last_var_type = $2;
+ if ($3 ne '' && substr ($3, -1) eq "\\") {
+ $last_var_value = substr ($3, 0, length ($3) - 1);
+ } else {
+ $last_var_value = $3;
+ }
+
+ if ($2 ne '+') {
+ $macros{$1} = { contents => $last_var_value,
+ atline => $.,
+ used => 0};
+ } else {
+ # Add to the previously defined var
+ if (defined ($macros{$1})) {
+ $macros{$1}{contents} .= ' ' . $last_var_value;
+ } else {
+ &report_warning (100, "$filename:$.: Macro '$1' not " .
+ "previously defined");
+ $macros{$1} = { contents => $last_var_value,
+ atline => $.,
+ used => 0};
+ }
+ };
+ } elsif (/$INCLUDE_PATTERN/o) {
+ &debug ("$filename:$.: Found include, FIXME: handle it!");
+ } else {
+ # This isn't an error; it is probably a continued rule.
+ # In fact, this is what we assume.
+
+ if (/^(\s+)/) {
+ if ($rule_indentation eq "") {
+ $rule_indentation = $1;
+ } elsif ($rule_indentation ne $1) {
+ &report_error (102, "$filename:$.: Incorrect indentation");
+ return {};
+ };
+ } else {
+ $prev_state = -1;
+ }
+
+ $last_var_name = "";
+
+ if ($prev_state == IN_RULE_DEF) {
+ $rules{$current_rule}{actions} .= $_;
+ } else {
+ push @extra_text, $_;
+ };
+ }
+
+ $saw_bk = $new_saw_bk;
+ $_ = <MAKEFILE>;
+ push @lines, $_;
+ };
+
+ &report_error (100, "trailing backslash on last line")
+ if $saw_bk;
+
+ close MAKEFILE;
+
+ my $makefile = { filename => $filename,
+ lines => \ lines,
+ macros => \%macros,
+ rules => \%rules,
+ extra => \ extra_text,
+ dirty => 0 };
+
+ return $makefile;
+}
+
+sub read_entire_file ($)
+{
+ my $filename = shift;
+ my $file_contents = "";
+ my $file_offset = 0;
+ my $bytes_read = 0;
+
+ if (!open MYFILE, $filename) {
+ report_error 100, "Can't open file $filename: $!";
+ return "";
+ };
+
+ while ($bytes_read = read MYFILE, $file_contents, 4096, $file_offset) {
+ $file_offset += $bytes_read;
+ };
+ close MYFILE;
+
+ return $file_contents;
+}
+
+sub extract_syntactic_arguments ($$$$)
+{
+ my ($string, $starting, $opening, $closing) = @_;
+
+ my $working_string = $string;
+ # offset always refers to $working_string
+ my $offset = 0;
+
+ my @result = ();
+
+ while ($working_string) {
+ # start by searching the starting pattern
+ if ($working_string =~ m/$starting/) {
+ # $balance keeps track of the paren count and starts at one because
+ # the opening paren must be included in the starting pattern
+ my $balance = 1;
+ # $index is the offset at which the starting pattern was found
+ my $index;
+
+ # chop $working_string up to the starting pattern
+ $working_string = substr $working_string, $+[0];
+ $offset += $+[0];
+ $index = $offset;
+
+ while ($working_string && $balance > 0) {
+ # search the first occurrence of $opening, $closing
+ # or an escaping backslash
+ my $found = ($working_string =~ m/($opening|$closing|\\)/);
+
+ if ($found) {
+ my ($match, $start, $end) = ($1, $-[1], $+[1]);
+
+ if ($match eq "\\") {
+ # just skip next character
+ $end++;
+ }
+ elsif ($match =~ m/$opening/) {
+ # increment paren balance
+ $balance++;
+ }
+ else {
+ # decrement paren balance
+ $balance--;
+
+ if ($balance == 0) {
+ my $result_length = $offset - $index + $start;
+ my $result_string = substr $string, $index, $result_length;
+ push @result, [ $index, $result_string ];
+ };
+ };
+
+ # chop already processed portion of $working_string
+ $working_string = substr $working_string, $end;
+ $offset += $end;
+
+ } else {
+ # unbalanced parentheses
+ $working_string = "";
+ last;
+ };
+ };
+
+ } else {
+ # no more matches
+ last;
+ };
+ };
+
+ return @result;
+}
+
+sub get_config_files ($)
+{
+ my $contents = shift;
+
+ my @tmp = &extract_syntactic_arguments ($contents, "AC_CONFIG_FILES\\(", "\\(", "\\)");
+ @tmp = &extract_syntactic_arguments ($contents, "AC_OUTPUT\\(", "\\(", "\\)") if ! @tmp;
+
+ if (@tmp) {
+ # get first match...
+ my ($offset, $tmp) = @{$tmp[0]};
+ # and remove possibly enclosing square brackets
+ ($tmp) = split /\s*,\s*/, $tmp;
+ if ($tmp =~ m/\s*\[ ( [^\]]+ ) \]/x) {
+ $tmp = $1;
+ $offset += $-[1];
+ };
+ return ($offset, $tmp);
+ } else {
+ return ();
+ };
+}
+
+sub parse_configure_in_buffer
+{
+ my ($config_file, $contents) = @_;
+
+ # get configured files, either from AC_OUTPUT or from AC_CONFIG_FILES
+ my (undef, $output) = &get_config_files ($contents);
+
+ # get substed vars
+ my (%vars, %tmpvars, @vars_order);
+ my @tmp = &extract_syntactic_arguments ($contents, "AC_SUBST\\(", "\\(", "\\)");
+ # catch variable definitions
+ while ($contents =~ m/(\w+)=(.+)$/mg) {
+ $tmpvars{$1} = $2;
+ };
+
+ # Only store those AC_SUBSTed vars
+ foreach my $tmp (@tmp) {
+ my (undef, $varname) = @$tmp;
+ if (defined ($tmpvars{$varname})) {
+ $vars{$varname} = $tmpvars{$varname};
+ push (@vars_order, $varname);
+ };
+ };
+
+ # Process AC_OUTPUT
+ my %other_files;
+
+ if (!defined ($output)) {
+ report_warning 101, "Missing AC_OUTPUT or AC_CONFIG_FILES";
+ $output = "";
+ };
+
+ GENFILE:
+ foreach my $file (split /\s+/, &trim ($output)) {
+ my ($gen, $input) = split /:/, $file;
+ if (!defined ($input)) {
+ $input = "$gen.in";
+ };
+
+ # Skip makefiles
+ if ($gen =~ /Makefile[^\/]*\z/) {
+ next GENFILE;
+ };
+
+ $other_files{$gen} = $input;
+ };
+
+ my $configure_in = { filename => $config_file,
+ contents => $contents,
+ other_files => \%other_files,
+ vars => \%vars,
+ vars_order => \ vars_order,
+ dirty => 0 };
+
+ return $configure_in;
+}
+
+sub parse_configure_in ($)
+{
+ my $project_dir = shift;
+ my $config_file;
+
+ if (-e "$project_dir/configure.ac") {
+ $config_file = "$project_dir/configure.ac"
+ } elsif (-e "$project_dir/configure.in") {
+ $config_file = "$project_dir/configure.in";
+ } else {
+ &report_error (100, "Neither configure.in nor configure.ac exist");
+ return {};
+ };
+
+ my $contents = &read_entire_file ($config_file);
+ if (! $contents) {
+ return {};
+ };
+ return parse_configure_in_buffer ($config_file, $contents);
+}
+
+sub configure_rewrite_packages
+{
+ my ($configure_in, $module, $packages) = @_;
+
+ $packages =~ s/,/ /sg;
+ $packages =~ s/ +/ /sg;
+ # Module is defined.
+ if ($configure_in->{'contents'} =~ s/(PKG_CHECK_MODULES\([\s\[]*$module[\s\]]*,[\s\[]*)[^\,\)\]]+(.*?\))/$1$packages$2/sm) {
+ } else {
+ ## Could not find any proper place to put the new variable.
+ ## Just putting it on top.
+ $configure_in->{'contents'} =~ s/AC_OUTPUT/PKG_CHECK_MODULES($module, $packages)\nAC_OUTPUT/sm;
+ }
+ $configure_in->{"dirty"} = 1;
+}
+
+sub configure_rewrite_variable
+{
+ my ($configure_in, $var, $value) = @_;
+
+ if ($configure_in->{'contents'} =~ /^\Q$var\E=/m) {
+ $configure_in->{'contents'} =~
+ s/^\Q$var\E=.*$/$var=$value/m;
+ } elsif ($configure_in->{'contents'} =~ /^AC_INIT\(.*?\)\s*$/m) {
+ $configure_in->{'contents'} =~
+ s/^(AC_INIT\(.*?\))\s*$/$1\n\n$var=$value\nAC_SUBST($var)\n/m;
+ } else {
+ ## Could not find any proper place to put the new variable.
+ ## Just putting it on top.
+ $configure_in->{'contents'} =
+ "$var=$value\nAC_SUBST($var)\n". $configure_in->{'contents'};
+ }
+ $configure_in->{"dirty"} = 1;
+}
+
+sub configure_remove_variable
+{
+ my ($configure_in, $var) = @_;
+ if ($configure_in->{'contents'} =~ /^\Q$var\E=/m)
+ {
+ $configure_in->{'contents'} =~
+ s/^\Q$var\E=.*$//m;
+ $configure_in->{dirty} = 1;
+ }
+}
+
+sub configure_rewrite_package_info
+{
+ my ($configure_in, $package_name, $package_version, $package_url) = @_;
+
+ if ($package_url ne "")
+ {
+ $configure_in->{contents} =~ s/AC_INIT\(.*?\)/AC_INIT($package_name, $package_version, $package_url)/sm;
+ }
+ elsif ($package_version ne "")
+ {
+ $configure_in->{contents} =~ s/AC_INIT\(.*?\)/AC_INIT($package_name, $package_version)/sm;
+ }
+ elsif ($package_name ne "")
+ {
+ $configure_in->{contents} =~ s/AC_INIT\(.*?\)/AC_INIT($package_name)/sm;
+ }
+
+ $configure_in->{dirty} = 1;
+}
+
+######################################################################
+# Direct file modification functions
+######################################################################
+
+sub output_am_file
+{
+ my ($makefile, $new_file) = @_;
+
+ rename ($new_file, "${new_file}.bak");
+
+ open NEWFILE, "> $new_file" ||
+ return &report_error (3, "Can't open $new_file for writing");;
+
+ my $last_line = scalar (@{$makefile->{lines}});
+ my $current_line = 1;
+ foreach my $line (@{$makefile->{lines}}) {
+ chomp $line;
+
+ ## last line is empty, do not output a newline together with it.
+ if (($current_line == $last_line) && $line eq "") {
+ print NEWFILE $line;
+ } else {
+ print NEWFILE $line, "\n";
+ }
+ $current_line++;
+ }
+ close NEWFILE;
+ return 0;
+}
+
+sub output_configure_in ($)
+{
+ my ($configure_in) = @_;
+ my $filename = $configure_in->{filename};
+
+ rename ($filename, "${filename}.bak");
+
+ open NEWFILE, "> $filename" ||
+ return &report_error (3, "Can't open $filename for writing");;
+ print NEWFILE $configure_in->{contents};
+ close NEWFILE;
+
+ $configure_in->{dirty} = 0;
+
+ return 0;
+}
+
+sub file_insert_lines
+{
+ # Insert a line of text
+ my $makefile = shift;
+ my $at = shift;
+ my @new_lines = @_;
+
+ if (scalar @new_lines == 0) {
+ return;
+ }
+
+ if ($at < 1 || $at > $#{$makefile->{lines}} + 2) {
+ &report_warning (301, "Out of bounds line number $at. " .
+ "Will insert at the end\n");
+ $at = $#{$makefile->{lines}} + 2;
+ };
+
+ my $count = $#new_lines + 1;
+ splice @{$makefile->{lines}}, $at - 1, 0, @new_lines;
+ $makefile->{dirty} = 1;
+
+ foreach my $macro (values %{$makefile->{macros}}) {
+ $macro->{atline} += $count if ($macro->{atline} >= $at);
+ };
+ foreach my $rule (values %{$makefile->{rules}}) {
+ $rule->{atline} += $count if ($rule->{atline} >= $at);
+ };
+}
+
+sub file_remove_lines
+{
+ # Remove a line of text
+ my ($makefile, $at, $count, $remove_trailing_blank) = @_;
+
+ if ($count <= 0) {
+ return;
+ }
+
+ if ($at < 1 || $at > $#{$makefile->{lines}} + 2) {
+ &report_warning (301, "Out of bounds line number $at. " .
+ "I will not remove it\n");
+ return;
+ };
+
+ $_ = @{$makefile->{lines}}[$at + $count - 1];
+ if ($at + $count < scalar @{$makefile->{lines}} &&
+ /^\s+$/ && $remove_trailing_blank) {
+ $count++;
+ };
+ &debug ("Removing lines from $at to " . ($at + $count));
+ splice @{$makefile->{lines}}, $at - 1, $count;
+ $makefile->{dirty} = 1;
+
+ foreach my $macro (values %{$makefile->{macros}}) {
+ $macro->{atline} -= $count if ($macro->{atline} > $at);
+ };
+ foreach my $rule (values %{$makefile->{rules}}) {
+ $rule->{atline} -= $count if ($rule->{atline} > $at);
+ };
+}
+
+sub get_line_from_hint
+{
+ # Return the nearest line to hint into which to insert contents
+ my ($makefile, $hint) = @_;
+
+ # Add at the end by default
+ my $leading_blank = 0;
+ my $trailing_blank = 0;
+
+ my $total = $#{$makefile->{lines}} + 1;
+ my $line = $total + 1;
+
+ THINGS:
+ foreach my $things ($makefile->{macros}, $makefile->{rules}) {
+ foreach my $thing_name (keys %$things) {
+ if ($thing_name eq $hint) {
+ $line = $things->{$thing_name}{atline} + 1;
+
+ # Now find the next available line
+ my $continued = $makefile->{lines}[$line - 2] =~ /\\$/;
+
+ while ($line <= $total) {
+ $_ = $makefile->{lines}[$line - 1];
+ if (/\\$/) {
+ # Line is continued, skip next too
+ $continued = 1;
+ $line++;
+ } elsif (/^\t/ || $continued) {
+ # Last line was continued or line starts with a tab char,
+ # so it's a rule action
+ $line++;
+ $continued = 0;
+ } else {
+ # An available line here
+ # If it's blank, skip it to produce prettier output
+ if (/^\s*$/) {
+ $line++;
+ $leading_blank = 1;
+ };
+ last;
+ };
+ };
+ last THINGS;
+ };
+ };
+ };
+
+ if ($line < $total) {
+ $trailing_blank = int ($makefile->{lines}[$line - 1] =~ /^\s+$/);
+ };
+
+ &debug ("Hint for $hint is ($line, $leading_blank, $trailing_blank)");
+ return ($line, $leading_blank, $trailing_blank);
+}
+
+
+######################################################################
+# Macro editing functions
+######################################################################
+
+sub last_macro
+{
+ # Returns last (in the text file) defined macro
+ my $makefile = $_[0];
+
+ my %macros = %{$makefile->{macros}};
+
+ my $line = 0;
+ my $macro_name = "";
+
+ foreach my $tmp (keys %macros) {
+ if (!$macro_name || $macros{$tmp}{atline} > $line) {
+ $macro_name = $tmp;
+ $line = $macros{$tmp}{atline};
+ }
+ }
+ return $macro_name;
+}
+
+sub analyze_macro_style
+{
+ my ($makefile, $macro_name) = @_;
+
+ my $macro = $makefile->{macros}{$macro_name};
+
+ if (!defined $macro) {
+ return (0, %default_macro_style);
+ }
+
+ # Analyze the current macro text and get the backslash column and the
+ # items per line
+ my $bs_column = 0; # backslash column.
+ my $items_per_line = 1; # number of text elements per text line
+ my $line = "";
+ my $line_num = $macro->{atline} - 1;
+ my $first_line = 1;
+ my $first_line_ignored = 0;
+ my $leading_space;
+
+ # Iterate over the continued lines
+ do {
+ $line_num++;
+ $line = @{$makefile->{lines}}[$line_num - 1];
+
+ my $line_copy = &expand_tabs ($line);
+ my $bs = $bs_column;
+
+ if ($line_copy =~ /\\\s*$/) {
+ # first get the column of the backslash
+ $bs = rindex $line_copy, '\\';
+ };
+
+ if ($first_line) {
+ $line =~ /$ASSIGNMENT_PATTERN/;
+ $line_copy = &empty_if_undef ($3);
+ } elsif (!$leading_space) {
+ $line =~ /^(\s+)\S+/;
+ $leading_space = $1;
+ };
+ $line_copy =~ s/\\\s*$//;
+
+ my @items = split /\s+/, &trim ($line_copy);
+
+ if ($first_line && scalar @items == 0) {
+ $first_line_ignored = 1;
+ };
+
+ # get the maximum items per line seen
+ if (scalar @items + 1 > $items_per_line) {
+ $items_per_line = scalar @items;
+ };
+
+ if ((!$first_line && $bs_column == 0) || ($first_line && $first_line_ignored)) {
+ # initialize bs_column
+ $bs_column = $bs;
+ } elsif ($bs_column != 0 && $bs != $bs_column) {
+ # bs column changed, get amount of separation
+ chomp $line_copy;
+ if ($line_copy =~ s/.+[^ ](\s+)\z/$1/) {
+ $bs_column = - length ($line_copy);
+ } else {
+ ## Line has zero spacing. So use 1 instead.
+ $bs_column = -1;
+ };
+ };
+
+ $first_line = 0;
+ } while ($line =~ /\\\s*$/);
+
+ &debug ("Macro: $macro_name, " .
+ "bs_column = $bs_column, items_per_line = $items_per_line, ".
+ "last_line = $line_num, ignore_first = $first_line_ignored");
+
+ my %macro_style = ( bs_column => $bs_column,
+ items_per_line => $items_per_line,
+ first_line_ignored => $first_line_ignored,
+ leading_space => $leading_space );
+
+ return ($line_num, \%macro_style);
+}
+
+sub format_macro
+{
+ my ($line, $text_to_add, $macro_style, $add_trailing_bs) = @_;
+
+ my (@result, $one_line, $item_count, @items);
+
+ @items = split /\s+/, &trim ($text_to_add);
+
+ if (!defined ($macro_style)) {
+ $macro_style = \%default_macro_style;
+ };
+
+ my $leading_space = $macro_style->{leading_space};
+ my $bs_column = $macro_style->{bs_column};
+
+ if (!defined ($leading_space)) {
+ $leading_space = "\t";
+ }
+
+ if ($line =~ /$ASSIGNMENT_PATTERN/) {
+ my @current_items = split /\s+/, &trim (&empty_if_undef ($3));
+ $item_count = scalar @current_items;
+
+ if (!$bs_column) {
+ if ($item_count == 1) {
+ $bs_column = -1;
+ } else {
+ $bs_column = $default_macro_style{bs_column};
+ };
+ };
+ my $line_copy = &expand_tabs ($line);
+ if ($bs_column > 0 && length ($line_copy) + 1 > $bs_column) {
+ $bs_column = length ($line_copy) + 1;
+ };
+
+ } else {
+ # Extract leading space
+ $line =~ /^(\s*)\S+/;
+ $leading_space = $1;
+
+ # count items already in
+ my @current_items = split /\s+/, &trim ($line);
+ $item_count = scalar @current_items;
+ }
+
+ my $items_per_line = $macro_style->{items_per_line};
+ if (!$items_per_line) {
+ if ($bs_column < 0) {
+ $items_per_line = 1;
+ } else {
+ my $avg_width = 0;
+ foreach (@items) {
+ $avg_width += length ($_);
+ };
+ $avg_width /= ($#items + 1);
+ # Wild guess here... could be adjusted
+ $items_per_line = int ($bs_column * .75 / $avg_width);
+ };
+ };
+
+ $one_line = $line;
+ $one_line =~ s/\s+$/ /;
+ my $max_width = ($bs_column > 0 ? $bs_column : 70); # FIXME: don't hardcode
+
+ &debug ("Formatting '$one_line' which already has $item_count items, ".
+ "bs_column = $bs_column, items_per_line = $items_per_line");
+
+ my $ignore_first_line = $macro_style->{first_line_ignored};
+
+ foreach my $item (@items) {
+ my $line_copy = &expand_tabs ($one_line);
+
+ # if items_per_line is more than 2 we assume the user wants to fill in
+ # the space until the bs column (or max_width,
+ # but normally, when bs_column < 0, items_per_line will be 1)
+ if (($items_per_line <= 2 && $item_count >= $items_per_line) ||
+ length ($line_copy) + length ($item) > $max_width ||
+ $ignore_first_line) {
+
+ $ignore_first_line = 0;
+ # close this line
+ $one_line =~ s/\s+$//;
+ if ($bs_column > 0) {
+ # we need the bs at a fixed column
+ $one_line .= " " x ($bs_column - length ($line_copy));
+ } else {
+ $one_line .= " " x (- $bs_column);
+ }
+ $one_line .= "\\\n";
+ push @result, $one_line;
+ $one_line = $leading_space;
+ $item_count = 0;
+ }
+ $one_line .= ($item_count ? ' ' : '' ) . "$item";
+ $item_count++;
+ };
+ # Add last line
+ if ($add_trailing_bs) {
+ my $line_copy = &expand_tabs ($one_line);
+
+ # close this line
+ $one_line =~ s/\s+$//;
+ if ($bs_column > 0) {
+ # we need the bs at a fixed column
+ $one_line .= " " x ($bs_column - length ($line_copy));
+ } else {
+ $one_line .= " " x (- $bs_column);
+ }
+ $one_line .= "\\\n";
+
+ } else {
+ $one_line .= "\n";
+ }
+
+ push @result, $one_line;
+
+ return @result;
+}
+
+sub macro_prepend_text
+{
+ # Prepends some text to the macro
+ my ($makefile, $macro_name, $text) = @_;
+
+ my $macro = $makefile->{macros}{$macro_name};
+ if (!defined ($macro)) {
+ # If the macro doesn't exist create it
+ ¯o_create ($makefile, $macro_name, $text, &last_macro ($makefile));
+
+ } elsif ($macro->{atline} > 0) {
+ my ($last_line_num, $macro_style) =
+ &analyze_macro_style ($makefile, $macro_name);
+ my $first_line_num = $macro->{atline};
+ my $first_line = @{$makefile->{lines}}[$first_line_num - 1];
+
+ $first_line =~ /$ASSIGNMENT_PATTERN/;
+ my $leading_text = $3;
+ $text .= " " . $leading_text;
+ $text =~ s/\\\s*$//;
+ $first_line = substr $first_line, 0, -length ($leading_text) - 1;
+
+ my @lines = &format_macro ($first_line, $text, $macro_style,
+ ($last_line_num != $first_line_num));
+
+ $first_line = shift @lines;
+ @{$makefile->{lines}}[$first_line_num - 1] = $first_line;
+ $makefile->{dirty} = 1;
+ &file_insert_lines ($makefile, $first_line_num + 1, @lines);
+
+ # FIXME: this is not right, since maybe $text doesn't have backslashes
+ # and newlines
+ $macro->{contents} = $text . " " . $macro->{contents};
+ }
+}
+
+sub macro_append_text
+{
+ # Appends some text to the macro
+ my ($makefile, $macro_name, $text) = @_;
+
+ my $macro = $makefile->{macros}{$macro_name};
+ if (!defined ($macro)) {
+ # If the macro doesn't exist create it
+ ¯o_create ($makefile, $macro_name, $text, &last_macro ($makefile));
+
+ } elsif ($macro->{atline} > 0) {
+ my ($last_line_num, $macro_style) =
+ &analyze_macro_style ($makefile, $macro_name);
+ my $last_line = @{$makefile->{lines}}[$last_line_num - 1];
+ my @lines = &format_macro ($last_line, $text, $macro_style, 0);
+
+ $last_line = shift @lines;
+ @{$makefile->{lines}}[$last_line_num - 1] = $last_line;
+ $makefile->{dirty} = 1;
+ &file_insert_lines ($makefile, $last_line_num + 1, @lines);
+
+ # FIXME: this is not right, since maybe $text doesn't have backslashes
+ # and newlines
+ $macro->{contents} .= " $text";
+ }
+}
+
+sub macro_create
+{
+ # hint is a macro name. This macro should be created around it.
+ my ($makefile, $macro_name, $initial, $hint_near) = @_;
+
+ my $macros = $makefile->{macros};
+ if (!$hint_near) {
+ $hint_near = &last_macro ($makefile);
+ };
+
+ my ($line, $lead, $trail) = &get_line_from_hint ($makefile, $hint_near);
+
+ if (defined ($macros->{$macro_name})) {
+ $line = $macros->{$macro_name}{atline};
+ &report_warning (300, "Macro '$macro_name' already exists. It will be deleted");
+ ¯o_remove ($makefile, $macro_name);
+ $lead = 1;
+ $trail = 0;
+ };
+
+ &debug ("macro_append: Using line $line");
+
+ &file_insert_lines ($makefile, $line, "\n") if (!$trail);
+ &file_insert_lines ($makefile, $line,
+ &format_macro ("$macro_name = ", $initial));
+ &file_insert_lines ($makefile, $line++, "\n") if !$lead;
+
+ # FIXME: this is not right, since maybe $initial doesn't have backslashes
+ # and newlines
+ my %new_macro = ( contents => $initial,
+ atline => $line,
+ used => 0 );
+ $macros->{$macro_name} = \%new_macro;
+}
+
+sub macro_remove
+{
+ my $makefile = shift;
+
+ while (my $macro_name = shift) {
+ my $macro = $makefile->{macros}{$macro_name};
+ if (!defined ($macro)) {
+ next;
+ }
+
+ my $line = $macro->{atline};
+ if ($line > 0) {
+ &debug ("Removing macro $macro_name at line $line");
+
+ my $count = 1;
+ while (@{$makefile->{lines}}[$line + $count - 2] =~ /\\\s*$/) {
+ $count++;
+ };
+
+ &file_remove_lines ($makefile, $line, $count, 1);
+
+ delete $makefile->{macros}{$macro_name};
+ };
+ }
+}
+
+sub macro_rewrite
+{
+ # Rewrites the macro with the new text, but trying to accomodate to the
+ # previous style
+ my ($makefile, $macro_name, $text, $hint_near) = @_;
+
+ my $macro = $makefile->{macros}{$macro_name};
+ if (!defined ($macro)) {
+ if ($hint_near eq "") {
+ $hint_near = &last_macro ($makefile);
+ }
+ # If the macro doesn't exist create it
+ ¯o_create ($makefile, $macro_name, $text, $hint_near);
+
+ } elsif ($macro->{atline} > 0) {
+ my ($last_line_num, $macro_style) =
+ &analyze_macro_style ($makefile, $macro_name);
+ my $first_line_num = $macro->{atline};
+
+ my @lines = &format_macro ("$macro_name = ", $text, $macro_style, 0);
+
+ &file_remove_lines ($makefile, $first_line_num,
+ $last_line_num - $first_line_num + 1);
+ &file_insert_lines ($makefile, $first_line_num, @lines);
+
+ # FIXME: this is not right, since maybe $text doesn't have backslashes
+ # and newlines
+ $macro->{contents} = $text;
+ }
+}
+
+sub macro_remove_text
+{
+ # Removes some text from the macro definition
+ my ($makefile, $macro_name, $remove_text, $remove_macro_if_empty) = @_;
+
+ my $macro = $makefile->{macros}{$macro_name};
+ if (!defined ($macro) || $macro->{atline} <= 0) {
+ return;
+ };
+
+ $macro->{contents} =~ s/$remove_text//;
+ if (&trim ($macro->{contents}) eq "" && $remove_macro_if_empty) {
+ ¯o_remove ($makefile, $macro_name);
+ return;
+ }
+
+ my ($last_line_num, $macro_style) = &analyze_macro_style ($makefile, $macro_name);
+ my $line_num = $macro->{atline};
+ while ($line_num <= $last_line_num) {
+ my $line = @{$makefile->{lines}}[$line_num - 1];
+
+ if ($line =~ /(\s|=)$remove_text\s/) {
+ &debug ("found the text to remove at line $line_num");
+ # Found the text
+ # Check if it's the only thing in the line
+ if ($line =~ /^\s+$remove_text\s*\\?$/) {
+ &file_remove_lines ($makefile, $line_num, 1);
+ if ($line_num == $last_line_num) {
+ # remove the trailing bs of the previous line
+ $_ = @{$makefile->{lines}}[$line_num - 2];
+ chomp; chop;
+ @{$makefile->{lines}}[$line_num - 2] = $_ . "\n";
+ $makefile->{dirty} = 1;
+ }
+ } else {
+ # else, just remove the text, the trailing bs and reformat the line
+ $line =~ s/$remove_text\s//;
+ $line =~ s/\\\s*$//;
+ if ($line !~ /^\s+/ && $line_num > $macro->{atline}) {
+ $line = $macro_style->{leading_space} . $line;
+ };
+ my @lines = &format_macro ($line, "", $macro_style,
+ ($line_num != $last_line_num));
+ @{$makefile->{lines}}[$line_num - 1] = shift @lines;
+ $makefile->{dirty} = 1;
+ if (@lines) {
+ &file_insert_lines ($makefile, $line_num + 1, @lines);
+ }
+ }
+ last;
+ };
+ $line_num++;
+ }
+}
+
+sub test_print_am_file
+{
+ my ($makefile, $message) = @_;
+
+ print "MAKEFILE CONTENTS $message\n" . "-" x 79 . "\n";
+ my $number = 1;
+ foreach my $line (@{$makefile->{lines}}) {
+ print "$number: $line";
+ $number++;
+ };
+}
+
+
+######################################################################
+# Rule editing functions
+######################################################################
+
+sub rule_remove
+{
+ my $makefile = shift;
+
+ while (my $rule_name = shift) {
+ my $rule = $makefile->{rules}{$rule_name};
+ if (!defined ($rule)) {
+ next;
+ }
+
+ my $line = $rule->{atline};
+ if ($line > 0) {
+ my $count = 1;
+ my $maxcount = scalar @{$makefile->{lines}} - $line;
+ # count dependency lines
+ while ($count < $maxcount &&
+ @{$makefile->{lines}}[$line + $count - 2] =~ /\\\s*$/) {
+ $count++;
+ };
+ # count action lines
+ while ($count <= $maxcount &&
+ @{$makefile->{lines}}[$line + $count - 1] =~ /^\t/) {
+ $count++;
+ };
+
+ &debug ("Removing rule $rule_name at line $line");
+
+ &file_remove_lines ($makefile, $line, $count, 1);
+
+ delete $makefile->{rules}{$rule_name};
+ };
+ }
+}
+
+
+######################################################################
+# Text editing functions
+######################################################################
+
+sub beginning_of_line ($$)
+{
+ my ($string, $offset) = @_;
+
+ # this automagically handles first lines,
+ # since rindex will return -1 in that case
+ return rindex ($string, "\n", $offset - 1) + 1;
+}
+
+sub next_line_offset ($$)
+{
+ my ($string, $offset) = @_;
+
+ my $next_line = index ($string, "\n", $offset) + 1;
+ # make correction if at end of string and no terminating newline
+ $next_line = length $string if $next_line <= $offset;
+
+ return $next_line;
+}
+
+sub get_line_at_offset ($$)
+{
+ my ($string, $offset) = @_;
+
+ my $line_begin = beginning_of_line $string, $offset;
+ my $line_end = next_line_offset $string, $offset;
+
+ return substr $string, $line_begin, ($line_end - $line_begin);
+}
+
+sub stat_sample (@)
+{
+ my ($sum, $count, $max, $i) = (0,0,0,0);
+ my ($most_freq, $count_most_freq) = (0,0);
+ foreach my $data (@_) {
+ $sum += $i * $data;
+ $count += $data;
+ $max = $i if $data > 0;
+ if ($data >= $count_most_freq) {
+ ($most_freq, $count_most_freq) = ($i, $data);
+ };
+ $i++;
+ };
+ return ($max, int ($sum / $count), $most_freq, $count_most_freq / $count);
+}
+
+# return values:
+# 0 means unordered
+# +1 means descending
+# -1 means ascending
+sub analyze_order (@)
+{
+ my $order = undef;
+ my $last = shift;
+ foreach my $item (@_) {
+ my $current = $last cmp $item;
+ if ($current != 0) {
+ if (!defined $order) {
+ $order = $current;
+ } elsif ($current != $order) {
+ $order = 0;
+ last;
+ };
+ };
+ $last = $item;
+ };
+ return $order;
+}
+
+sub find_best_match ($$@)
+{
+ my $order = shift;
+ my $ref = shift;
+ my ($winner, $score) = (undef, 0);
+
+ foreach my $item (@_) {
+ my $this_score = 0;
+ if ($order == 0) {
+ # find the nearest string
+ while (substr ($ref, $this_score, 1) eq substr ($item, $this_score, 1) &&
+ $this_score < length $ref) {
+ $this_score++;
+ };
+ } elsif ($order > 0) {
+ $this_score = $item cmp $ref;
+ } else {
+ $this_score = $ref cmp $item;
+ };
+
+ if ($this_score >= $score) {
+ ($winner, $score) = ($item, $this_score);
+ };
+ };
+ return $winner;
+}
+
+sub analyze_text ($$$)
+{
+ my ($string, $offset, $length) = @_;
+
+ # variables to keep information
+ my ($ignore_first_line, $ignore_last_line) = (0,0);
+ my (@widths, @left_margins, @right_margins, @item_counts);
+ my $backslashed = 0;
+
+ # acquire data to analyze
+ my $inner_text = substr $string, $offset, $length;
+ my $first_line_offset = beginning_of_line $string, $offset;
+ my $outer_text = substr ($string, $first_line_offset,
+ next_line_offset ($string, $offset + $length)
+ - $first_line_offset);
+
+ my @lines = split "\n", $outer_text;
+ my @inner_lines = split "\n", $inner_text;
+
+ $ignore_first_line = 1 if trim (chop_backslash ($inner_lines[0])) eq "";
+ $ignore_last_line = 1 if trim (chop_backslash ($inner_lines[ inner_lines - 1])) eq "";
+
+ my ($line_count, $line_number) = (0, 0);
+ foreach my $line (@lines) {
+ my $expanded_line = expand_tabs $line;
+ my $line_length = length $expanded_line;
+
+ # skip ignored lines
+ next if $line_number == 0 && $ignore_first_line;
+ next if $line_number == @lines - 1 && $ignore_last_line;
+
+ # sample data
+ $widths[$line_length]++;
+ $backslashed = 1 if $expanded_line =~ m/\\\s*$/;
+ $expanded_line = chop_backslash $expanded_line;
+ if ($line_number != 0 && $line_number != @lines - 1) {
+ $expanded_line =~ m/^(\s*)\S.*\S(\s*)$/;
+ $left_margins[length $1]++;
+ $right_margins[length $2]++;
+ };
+
+ my @items = split /\s+/, trim $expanded_line;
+ $item_counts[ items]++;
+
+ # finish this line
+ $line_count++;
+
+ } continue {
+ $line_number++;
+ };
+
+ # get information
+ my ($max_width, $avg_width, $width, $width_prop) = stat_sample @widths;
+ my (undef, undef, $left_margin) = stat_sample @left_margins;
+ my (undef, undef, $right_margin) = stat_sample @right_margins;
+ my ($item_count) = stat_sample @item_counts;
+
+ # determine if text is somehow ordered
+ my @items = split /\s+/, trim $inner_text;
+ my $order = analyze_order @items;
+
+# print "max_width => $max_width, avg_width => $avg_width, width => ".
+# ($width_prop > .75 ? $width : -1) .", ignore_first_line => $ignore_first_line, ".
+# "ignore_last_line => $ignore_last_line, left_margin => $left_margin, ".
+# "right_margin => $right_margin, items_per_line => $item_count, ".
+# "order => $order, backslashed => $backslashed\n";
+
+ # width = -1 means every line's width is just what's needed for that line
+ # and in that case, attention is taken to right_margin
+ # otherwise, it indicates the desired width for any new line
+ # and the right_margin adjusts to fit such width
+ return { max_width => $max_width,
+ avg_width => $avg_width,
+ width => $width_prop > .75 ? $width : -1,
+ ignore_first_line => $ignore_first_line,
+ ignore_last_line => $ignore_last_line,
+ left_margin => $left_margin,
+ right_margin => $right_margin,
+ items_per_line => $item_count,
+ order => $order,
+ backslashed => $backslashed };
+}
+
+# FIXME: probably format_macro can be replaced with this new (possibly
+# better thought) function
+sub format_line ($$$)
+{
+ my ($line, $style, $start_column) = @_;
+
+ $start_column = 0 if !defined $start_column;
+ my $expanded_line = expand_tabs $line;
+ my @line_items = split /\s+/, trim (chop_backslash $line);
+ my $item_count = scalar @line_items;
+ my $width = $style->{width} > 0 ? $style->{width} : $style->{max_width};
+
+# print "Formatting $line";
+
+ if ($item_count > 1 &&
+ (length ($expanded_line) + $start_column > $width ||
+ $item_count > $style->{items_per_line})) {
+
+ my $rest = $line;
+ my $new_line = "";
+ my @rest;
+ my ($at, $chunk);
+
+ # try to split the line as close to $style->{width} as possible
+ $item_count = 0;
+ $rest =~ m/^(\s*\S+)/;
+ $chunk = $1;
+ $at = $+[1];
+ do {
+ # add the chunk
+ $new_line .= "$chunk ";
+ $item_count++;
+ $rest = substr $rest, $at;
+
+ # match next chunk
+ $rest =~ m/^\s*(\S+)/;
+ $chunk = $1;
+ $at = $+[1];
+ } while (length (expand_tabs ("$new_line $chunk")) + $start_column < $width &&
+ $item_count < $style->{items_per_line});
+
+ # format the reduced line
+ $new_line = &format_line ("$new_line\n", $style, $start_column);
+
+ # correct left margin in $rest
+ $rest =~ s/^\s*//;
+ $rest = (" " x $style->{left_margin}) . $rest;
+
+ # recurse on $rest, which should still be a single line
+ $line = $new_line . &format_line ($rest, $style, 0);
+
+ } else {
+ # line fits and has a valid number of items... just close it
+ # remove trailing space
+ $line = chop_backslash $line;
+ $line =~ s/\s*$//;
+
+ if ($style->{width} > 0) {
+ # close line at a fixed length
+ $line .= " " x (length (expand_tabs $line) - $style->{width});
+ } else {
+ $line .= " " x $style->{right_margin};
+ };
+ if ($style->{backslashed}) {
+ $line .= "\\";
+ };
+ $line .= "\n";
+
+ };
+
+ return $line;
+}
+
+sub insert_text ($$$$)
+{
+ my ($string, $start_column, $new_text, $style) = @_;
+ my $result = "";
+
+ # first of all we need to find a place to put the new text on
+ my @string_items = split /\s+/, trim $string;
+ my $insert_offset;
+ my $reference = find_best_match $style->{order}, $new_text, @string_items;
+
+ if ($reference) {
+ my $tmp = " $string ";
+ while ($tmp =~ m/\s+(\Q$reference\E)\s+/g) {
+ $insert_offset = $+[1];
+ };
+
+ } else {
+ # append the text to the string if we couldn't find a reference
+ $insert_offset = beginning_of_line $string, length ($string);
+ $insert_offset = beginning_of_line $string, $insert_offset - 1
+ if $style->{ignore_last_line};
+ };
+
+ my ($before, $after) = (substr ($string, 0, $insert_offset),
+ substr ($string, $insert_offset));
+
+ # chop spaces at both ends
+ $before =~ s/\s*$//;
+ $after =~ s/^\s*//;
+
+ $result = "$before $new_text $after";
+
+ my $start_of_line_to_format = beginning_of_line $result, length $before;
+ my $line_to_format = get_line_at_offset $result, length $before;
+ my $formatted_line = format_line ($line_to_format, $style,
+ $start_of_line_to_format == 0 ?
+ $start_column : 0);
+
+ # replace the formatted line
+ substr $result, $start_of_line_to_format, length $line_to_format, $formatted_line;
+
+ return $result;
+}
+
+
+######################################################################
+# configure.in file editing functions
+######################################################################
+
+sub get_column ($$)
+{
+ my ($string, $offset) = @_;
+
+ my $column0 = beginning_of_line $string, $offset;
+ return length expand_tabs substr $string, $column0, ($offset - $column0);
+}
+
+sub edit_config_files ($$$)
+{
+ my ($configure_in, $action, $operand) = @_;
+ my ($offset, $config_files) = get_config_files $configure_in->{contents};
+
+ return unless defined $config_files;
+
+ # get current style
+ my $style = analyze_text $configure_in->{contents}, $offset, length $config_files;
+ my $new_config_files = $config_files;
+ my $start_column = get_column $configure_in->{contents}, $offset;
+
+ if ($action eq "add") {
+ $new_config_files = insert_text ($config_files, $start_column,
+ $operand, $style);
+
+ } elsif ($action eq "remove") {
+ $new_config_files =~ s/\Q$operand\E\s*//g;
+ };
+
+ # save result string
+ if ($new_config_files ne $config_files) {
+ substr $configure_in->{contents}, $offset, length $config_files, $new_config_files;
+ $configure_in->{dirty} = 1;
+ };
+}
+
+
+######################################################################
+# General Functions
+######################################################################
+
+sub am_file_reset
+{
+ my $makefile = shift;
+ foreach my $macro (values %{$makefile->{macros}}) {
+ $macro->{used} = 0;
+ }
+ foreach my $rule (values %{$makefile->{rules}}) {
+ $rule->{used} = 0;
+ }
+}
+
+sub use_macro
+{
+ my $macro = $_[0];
+ if (defined ($macro)) {
+ $macro->{used} = 1;
+ return &trim ($macro->{contents});
+ } else {
+ return undef ();
+ };
+}
+
+sub use_rule
+{
+ my $rule = $_[0];
+ if (defined ($rule)) {
+ $rule->{used} = 1;
+ return (&trim ($rule->{dependencies}), $rule->{actions});
+ } else {
+ return (undef, undef);
+ };
+}
+
+sub expand_one_var
+{
+ my ($source, %expansion_set) = @_;
+ my ($var_name, $repl);
+
+ # Expand normal macros
+ while ($source =~ /\$[({](\w*)[)}]/) {
+ $var_name = $1;
+ $repl = &empty_if_undef ($expansion_set{$var_name}{contents});
+ $source =~ s/\$[({]$var_name[)}]/$repl/;
+ }
+
+ # Expand substitution macros $(macro:search=replace)
+ while ($source =~ /\$\((\w*):([^=]+)=([^\)]*)\)/) {
+ $var_name = $1;
+ my ($search, $replace) = ($2, $3);
+
+ $repl = &empty_if_undef ($expansion_set{$var_name}{contents});
+ $repl =~ s/$search/$replace/g;
+
+ # Set sources list
+ $source =~ s/\$\($var_name:([^=]+)=([^\)]*)\)/$repl/;
+ }
+ return $source;
+}
+
+1;
Added: trunk/plugins/gbf-am/GBF/General.pm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/GBF/General.pm Wed Dec 31 11:55:52 2008
@@ -0,0 +1,120 @@
+package GBF::General;
+
+use Exporter;
+use strict;
+
+our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+ ISA = qw (Exporter);
+
+ EXPORT = qw (&report_error &report_warning
+ &trim &canonicalize_name &empty_if_undef &expand_tabs
+ &base_name &dir_name &chop_backslash &max &min);
+ EXPORT_OK = qw ($debug);
+%EXPORT_TAGS = ();
+
+
+
+######################################################################
+#### SUBROUTINES AND HELPER FUNCTIONS ##############################
+######################################################################
+
+sub report_error
+{
+ my ($code, $message) = @_;
+
+ print STDERR "ERROR($code): $message\n";
+
+ return $code;
+}
+
+sub report_warning
+{
+ my ($code, $message) = @_;
+
+ print STDERR "WARNING($code): $message\n";
+}
+
+sub canonicalize_name
+{
+ my ($name);
+
+ $name = $_[0];
+ $name =~ tr/\.-/_/;
+
+ return $name;
+}
+
+sub trim
+{
+ $_ = $_[0];
+
+ s/^\s*//;
+ s/\s*$//;
+
+ return $_;
+}
+
+sub empty_if_undef
+{
+ return defined ($_[0]) ? $_[0] : "";
+}
+
+sub expand_tabs
+{
+ $_ = $_[0];
+ my $i;
+ while (/\t/) {
+ $i = index ($_, "\t");
+ $i = $i % 8;
+ if ($i == 0) {
+ s/\t/ /;
+ } else {
+ my $pad = " " x (8 - $i);
+ s/\t/$pad/;
+ };
+ };
+ return $_;
+}
+
+sub base_name {
+ my ($name, $dir);
+
+ $name = $_[0];
+ $dir = dir_name ($name);
+ $name =~ s/$dir//;
+
+ return $name;
+}
+
+sub dir_name {
+ my ($name);
+
+ $name = $_[0];
+ $name =~ s/[^\/]+$//;
+
+ return $name;
+}
+
+sub chop_backslash ($)
+{
+ $_ = shift;
+ s/\\\s*$//;
+ return $_;
+}
+
+sub max
+{
+ my $max = shift;
+ foreach my $x (@_) { $max = $x if $x > $max; };
+ return $max;
+}
+
+sub min
+{
+ my $min = shift;
+ foreach my $x (@_) { $min = $x if $x < $min; };
+ return $min;
+}
+
+1;
Added: trunk/plugins/gbf-am/GBF/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/GBF/Makefile.am Wed Dec 31 11:55:52 2008
@@ -0,0 +1,5 @@
+perlmodulesdir = $(pkgdatadir)/GBF
+perlmodules_DATA = AmFiles.pm General.pm
+
+EXTRA_DIST = $(perlmodules_DATA)
+
Added: trunk/plugins/gbf-am/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/Makefile.am Wed Dec 31 11:55:52 2008
@@ -0,0 +1,70 @@
+# Plugin UI file
+plugin_uidir = $(anjuta_ui_dir)
+plugin_ui_DATA =
+
+# Plugin glade file
+plugin_gladedir = $(anjuta_glade_dir)
+plugin_glade_DATA = gbf-am-dialogs.glade
+
+# Plugin icon file
+plugin_pixmapsdir = $(anjuta_image_dir)
+plugin_pixmaps_DATA = gbf-am-plugin-48.png
+
+# Plugin scripts
+scriptsdir = $(bindir)
+scripts_SCRIPTS = gbf-am-parse
+
+# Plugin description file
+plugin_in_files = gbf-am.plugin.in
+%.plugin: %.plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugindir = $(anjuta_plugin_dir)
+plugin_DATA = $(plugin_in_files:.plugin.in=.plugin)
+
+AM_CPPFLAGS = \
+ $(WARN_CFLAGS) \
+ $(DEPRECATED_FLAGS) \
+ $(LIBANJUTA_CFLAGS) \
+ -DSCRIPTS_DIR=\"$(scriptsdir)\"
+
+plugin_LTLIBRARIES = \
+ libgbf-am.la
+
+libgbf_am_la_SOURCES = \
+ plugin.c \
+ gbf-am-project.c \
+ gbf-am-project.h \
+ gbf-am-config.c \
+ gbf-am-config.h \
+ gbf-am-properties.c \
+ gbf-am-properties.h
+
+libgbf_am_la_LDFLAGS = $(ANJUTA_PLUGIN_LDFLAGS)
+
+libgbf_am_la_LIBADD = \
+ $(LIBANJUTA_LIBS)
+
+# Test program
+
+noinst_PROGRAMS = test
+
+test_SOURCES = \
+ test.c
+
+test_LDADD = \
+ libgbf-am.la \
+ $(LIBANJUTA_LIBS)
+
+
+EXTRA_DIST = \
+ $(plugin_in_files) \
+ $(plugin_DATA) \
+ $(plugin_ui_DATA) \
+ $(plugin_pixmaps_DATA)
+
+DISTCLEANFILES = \
+ $(plugin_DATA) \
+ $(plugin_in_files)
+
+SUBDIRS = GBF
+
Added: trunk/plugins/gbf-am/gbf-am-config.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-config.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,385 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-am-config.c
+ *
+ * This file is part of the Gnome Build framework
+ * Copyright (C) 2002 Gustavo Giráez
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "gbf-am-config.h"
+
+typedef struct _GbfAmConfigEntry GbfAmConfigEntry;
+
+struct _GbfAmConfigEntry {
+ gchar *key;
+ GbfAmConfigValue *value;
+};
+
+struct _GbfAmConfigMapping {
+ GList *pairs;
+};
+
+
+GbfAmConfigValue *
+gbf_am_config_value_new (GbfAmValueType type)
+{
+ GbfAmConfigValue *new_value;
+
+ g_return_val_if_fail (type != GBF_AM_TYPE_INVALID, NULL);
+
+ new_value = g_new0 (GbfAmConfigValue, 1);
+ new_value->type = type;
+
+ switch (type) {
+ case GBF_AM_TYPE_STRING:
+ new_value->string = NULL;
+ break;
+ case GBF_AM_TYPE_MAPPING:
+ new_value->mapping = gbf_am_config_mapping_new ();
+ break;
+ case GBF_AM_TYPE_LIST:
+ new_value->list = NULL;
+ break;
+ default:
+ break;
+ }
+
+ return new_value;
+}
+
+void
+gbf_am_config_value_free (GbfAmConfigValue *value)
+{
+ if (value == NULL)
+ return;
+
+ switch (value->type) {
+ case GBF_AM_TYPE_STRING:
+ g_free (value->string);
+ value->string = NULL;
+ break;
+ case GBF_AM_TYPE_MAPPING:
+ gbf_am_config_mapping_destroy (value->mapping);
+ value->mapping = NULL;
+ break;
+ case GBF_AM_TYPE_LIST:
+ if (value->list) {
+ g_slist_foreach (value->list,
+ (GFunc) gbf_am_config_value_free,
+ NULL);
+ g_slist_free (value->list);
+ value->list = NULL;
+ }
+ break;
+ default:
+ g_warning ("%s", _("Invalid GbfAmConfigValue type"));
+ break;
+ }
+ g_free (value);
+}
+
+GbfAmConfigValue *
+gbf_am_config_value_copy (const GbfAmConfigValue *source)
+{
+ GbfAmConfigValue *value;
+ GSList *l;
+
+ if (source == NULL)
+ return NULL;
+
+ value = gbf_am_config_value_new (source->type);
+
+ switch (source->type) {
+ case GBF_AM_TYPE_STRING:
+ value->string = g_strdup (source->string);
+ break;
+ case GBF_AM_TYPE_MAPPING:
+ value->mapping = gbf_am_config_mapping_copy (source->mapping);
+ break;
+ case GBF_AM_TYPE_LIST:
+ value->list = NULL;
+ for (l = source->list; l; l = l->next) {
+ GbfAmConfigValue *new_value =
+ gbf_am_config_value_copy ((GbfAmConfigValue *)l->data);
+ value->list = g_slist_prepend (value->list, new_value);
+ }
+ value->list = g_slist_reverse (value->list);
+ break;
+ default:
+ g_warning ("%s", _("Invalid GbfAmConfigValue type"));
+ break;
+ }
+
+ return value;
+}
+
+void
+gbf_am_config_value_set_string (GbfAmConfigValue *value,
+ const gchar *string)
+{
+ g_return_if_fail (value != NULL && value->type == GBF_AM_TYPE_STRING);
+
+ if (value->string)
+ g_free (value->string);
+
+ value->string = g_strdup (string);
+}
+
+void
+gbf_am_config_value_set_list (GbfAmConfigValue *value,
+ GSList *list)
+{
+ GSList *l;
+
+ g_return_if_fail (value != NULL && value->type == GBF_AM_TYPE_LIST);
+
+ if (value->list) {
+ g_slist_foreach (value->list, (GFunc) gbf_am_config_value_free, NULL);
+ g_slist_free (value->list);
+ }
+
+ value->list = NULL;
+ for (l = list; l; l = l->next) {
+ GbfAmConfigValue *new_value = gbf_am_config_value_copy
+ ((GbfAmConfigValue *)l->data);
+ value->list = g_slist_prepend (value->list, new_value);
+ }
+
+ value->list = g_slist_reverse (value->list);
+}
+
+void
+gbf_am_config_value_set_list_nocopy (GbfAmConfigValue *value,
+ GSList *list)
+{
+ g_return_if_fail (value != NULL && value->type == GBF_AM_TYPE_LIST);
+
+ if (value->list) {
+ g_slist_foreach (value->list, (GFunc) gbf_am_config_value_free, NULL);
+ g_slist_free (value->list);
+ }
+ value->list = list;
+}
+
+void
+gbf_am_config_value_set_mapping (GbfAmConfigValue *value,
+ GbfAmConfigMapping *mapping)
+{
+ g_return_if_fail (value != NULL && value->type == GBF_AM_TYPE_MAPPING);
+
+ gbf_am_config_mapping_destroy (value->mapping);
+
+ value->mapping = mapping;
+}
+
+GbfAmConfigMapping *
+gbf_am_config_mapping_new (void)
+{
+ GbfAmConfigMapping *new_map;
+
+ new_map = g_new0 (GbfAmConfigMapping, 1);
+ new_map->pairs = NULL;
+
+ return new_map;
+}
+
+void
+gbf_am_config_mapping_destroy (GbfAmConfigMapping *mapping)
+{
+ GbfAmConfigEntry *entry;
+ GList *lp;
+
+ if (mapping == NULL)
+ return;
+
+ for (lp = mapping->pairs; lp; lp = lp->next) {
+ entry = (GbfAmConfigEntry *) lp->data;
+ gbf_am_config_value_free (entry->value);
+ g_free (entry->key);
+ g_free (entry);
+ }
+ g_list_free (mapping->pairs);
+ g_free (mapping);
+}
+
+GbfAmConfigMapping *
+gbf_am_config_mapping_copy (const GbfAmConfigMapping *source)
+{
+ GbfAmConfigMapping *new_map;
+ GList *lp;
+
+ if (source == NULL)
+ return NULL;
+
+ new_map = g_new0 (GbfAmConfigMapping, 1);
+ new_map->pairs = NULL;
+
+ for (lp = source->pairs; lp; lp = lp->next) {
+ GbfAmConfigEntry *new_entry, *entry;
+
+ entry = (GbfAmConfigEntry *) lp->data;
+ if (entry == NULL)
+ continue;
+
+ new_entry = g_new0 (GbfAmConfigEntry, 1);
+ new_entry->key = g_strdup (entry->key);
+ new_entry->value = gbf_am_config_value_copy (entry->value);
+ new_map->pairs = g_list_prepend (new_map->pairs, new_entry);
+ }
+
+ return new_map;
+}
+
+GbfAmConfigValue *
+gbf_am_config_mapping_lookup (GbfAmConfigMapping *mapping,
+ const gchar *key)
+{
+ GbfAmConfigEntry *entry = NULL;
+ GList *lp;
+
+ g_return_val_if_fail (mapping != NULL && key != NULL, NULL);
+
+ for (lp = mapping->pairs; lp; lp = lp->next) {
+ entry = (GbfAmConfigEntry *) lp->data;
+ if (!strcmp (entry->key, key))
+ break;
+ }
+
+ return (lp ? entry->value : NULL);
+}
+
+gboolean
+gbf_am_config_mapping_insert (GbfAmConfigMapping *mapping,
+ const gchar *key,
+ GbfAmConfigValue *value)
+{
+ GbfAmConfigEntry *entry = NULL;
+ GList *lp;
+ gboolean insert = TRUE;
+
+ g_return_val_if_fail (mapping != NULL && key != NULL, FALSE);
+
+ for (lp = mapping->pairs; lp; lp = lp->next) {
+ entry = (GbfAmConfigEntry *) lp->data;
+ if (!strcmp (entry->key, key)) {
+ insert = FALSE;
+ break;
+ }
+ }
+
+ if (insert) {
+ GbfAmConfigEntry *new_entry;
+
+ new_entry = g_new0 (GbfAmConfigEntry, 1);
+ new_entry->key = g_strdup (key);
+ new_entry->value = value;
+ mapping->pairs = g_list_prepend (mapping->pairs, new_entry);
+ }
+
+ return insert;
+}
+
+gboolean
+gbf_am_config_mapping_update (GbfAmConfigMapping *mapping,
+ const gchar *key,
+ GbfAmConfigValue *value)
+{
+ GbfAmConfigEntry *entry = NULL;
+ GList *lp;
+ gboolean update = TRUE;
+ gboolean insert = TRUE;
+
+ g_return_val_if_fail (mapping != NULL && key != NULL, FALSE);
+
+ for (lp = mapping->pairs; lp; lp = lp->next) {
+ entry = (GbfAmConfigEntry *) lp->data;
+ if (!strcmp (entry->key, key)) {
+ if (entry->value != value)
+ {
+ gbf_am_config_value_free (entry->value);
+ entry->value = value;
+ update = TRUE;
+ }
+ insert = FALSE;
+ break;
+ }
+ }
+
+ if (insert) {
+ GbfAmConfigEntry *new_entry;
+
+ new_entry = g_new0 (GbfAmConfigEntry, 1);
+ new_entry->key = g_strdup (key);
+ new_entry->value = value;
+ mapping->pairs = g_list_prepend (mapping->pairs, new_entry);
+ }
+
+ return update;
+}
+
+gboolean
+gbf_am_config_mapping_remove (GbfAmConfigMapping *mapping,
+ const gchar *key)
+{
+ GbfAmConfigEntry *entry = NULL;
+ GList *lp;
+ gboolean remove = FALSE;
+
+ g_return_val_if_fail (mapping != NULL && key != NULL, FALSE);
+
+ for (lp = mapping->pairs; lp; lp = lp->next) {
+ entry = (GbfAmConfigEntry *) lp->data;
+ if (!strcmp (entry->key, key)) {
+ remove = TRUE;
+ break;
+ }
+ }
+
+ if (remove) {
+ gbf_am_config_value_free (entry->value);
+ g_free (entry->key);
+ g_free (entry);
+ mapping->pairs = g_list_delete_link (mapping->pairs, lp);
+ }
+
+ return remove;
+}
+
+void
+gbf_am_config_mapping_foreach (GbfAmConfigMapping *mapping,
+ GbfAmConfigMappingFunc callback,
+ gpointer user_data)
+{
+ GbfAmConfigEntry *entry = NULL;
+ GList *lp;
+
+ g_return_if_fail (mapping != NULL && callback != NULL);
+
+ for (lp = mapping->pairs; lp; lp = lp->next) {
+ entry = (GbfAmConfigEntry *) lp->data;
+ callback (entry->key, entry->value, user_data);
+ }
+}
Added: trunk/plugins/gbf-am/gbf-am-config.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-config.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,86 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-am-config.h
+ *
+ * This file is part of the Gnome Build framework
+ * Copyright (C) 2002 Gustavo Giráez
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GBF_AM_CONFIG_H__
+#define __GBF_AM_CONFIG_H__
+
+#include <glib.h>
+
+/* config data structures */
+typedef enum {
+ GBF_AM_TYPE_INVALID,
+ GBF_AM_TYPE_STRING,
+ GBF_AM_TYPE_MAPPING,
+ GBF_AM_TYPE_LIST
+} GbfAmValueType;
+
+typedef struct _GbfAmConfigValue GbfAmConfigValue;
+typedef struct _GbfAmConfigMapping GbfAmConfigMapping;
+
+struct _GbfAmConfigValue {
+ GbfAmValueType type;
+ gchar *string;
+ GbfAmConfigMapping *mapping;
+ GSList *list;
+};
+
+typedef void (*GbfAmConfigMappingFunc) (const gchar *key,
+ GbfAmConfigValue *value,
+ gpointer user_data);
+/* ---------- public interface */
+
+GbfAmConfigValue *gbf_am_config_value_new (GbfAmValueType type);
+void gbf_am_config_value_free (GbfAmConfigValue *value);
+GbfAmConfigValue *gbf_am_config_value_copy (const GbfAmConfigValue *source);
+
+void gbf_am_config_value_set_string (GbfAmConfigValue *value,
+ const gchar *string);
+void gbf_am_config_value_set_list (GbfAmConfigValue *value,
+ GSList *list);
+void gbf_am_config_value_set_list_nocopy (GbfAmConfigValue *value,
+ GSList *list);
+void gbf_am_config_value_set_mapping (GbfAmConfigValue *value,
+ GbfAmConfigMapping *mapping);
+
+#define gbf_am_config_value_get_string(x) ((const gchar *)(((GbfAmConfigValue *)x)->string))
+#define gbf_am_config_value_get_list(x) ((GSList *)(((GbfAmConfigValue *)x)->list))
+#define gbf_am_config_value_get_mapping(x) ((GbfAmConfigMapping *)(((GbfAmConfigValue *)x)->mapping))
+
+GbfAmConfigMapping *gbf_am_config_mapping_new (void);
+void gbf_am_config_mapping_destroy (GbfAmConfigMapping *mapping);
+GbfAmConfigMapping *gbf_am_config_mapping_copy (const GbfAmConfigMapping *source);
+GbfAmConfigValue *gbf_am_config_mapping_lookup (GbfAmConfigMapping *mapping,
+ const gchar *key);
+gboolean gbf_am_config_mapping_insert (GbfAmConfigMapping *mapping,
+ const gchar *key,
+ GbfAmConfigValue *value);
+gboolean gbf_am_config_mapping_update (GbfAmConfigMapping *mapping,
+ const gchar *key,
+ GbfAmConfigValue *value);
+gboolean gbf_am_config_mapping_remove (GbfAmConfigMapping *mapping,
+ const gchar *key);
+void gbf_am_config_mapping_foreach (GbfAmConfigMapping *mapping,
+ GbfAmConfigMappingFunc callback,
+ gpointer user_data);
+
+#endif /* __GBF_AM_CONFIG_H__ */
Added: trunk/plugins/gbf-am/gbf-am-dialogs.glade
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-dialogs.glade Wed Dec 31 11:55:52 2008
@@ -0,0 +1,571 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd";>
+
+<glade-interface>
+
+<widget class="GtkWindow" id="project_properties_dialog">
+ <property name="title" translatable="yes">window1</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+
+ <child>
+ <widget class="GtkNotebook" id="top_level">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">True</property>
+ <property name="tab_pos">GTK_POS_TOP</property>
+ <property name="scrollable">False</property>
+ <property name="enable_popup">False</property>
+
+ <child>
+ <widget class="GtkTable" id="general_properties_table">
+ <property name="visible">True</property>
+ <property name="n_rows">7</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">0</property>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">General</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="height_request">300</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <property name="spacing">5</property>
+
+ <child>
+ <widget class="GtkButton" id="add_module_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">0</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-add</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Add _module</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="add_package_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">0</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="stock">gtk-add</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Add _Package</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="remove_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-remove</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="packages_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Packages</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox2">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <property name="spacing">5</property>
+
+ <child>
+ <widget class="GtkButton" id="add_variable_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-add</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="remove_variable_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-remove</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="variables_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Variables</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+<widget class="GtkDialog" id="package_selection_dialog">
+ <property name="title" translatable="yes">Select package</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">600</property>
+ <property name="default_height">500</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="has_separator">True</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="cancelbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="okbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-add</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-3</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="pkg_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Select Package to add:</b></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
Added: trunk/plugins/gbf-am/gbf-am-parse.in
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-parse.in Wed Dec 31 11:55:52 2008
@@ -0,0 +1,2612 @@
+#!/usr/bin/perl -w
+# -*- perl -*-
+
+eval 'exec @PERL@ -S $0 ${1+"$@"}'
+ if 0;
+
+BEGIN
+{
+ my $prefix = "@prefix@";
+ my $perlmodulesdir = "@datadir@/@PACKAGE@";
+ unshift @INC, "$perlmodulesdir";
+}
+
+use strict;
+
+use GBF::General;
+use GBF::AmFiles;
+
+# I18n
+use POSIX;
+use Locale::gettext qw(textdomain gettext);
+
+setlocale(LC_MESSAGES, "");
+textdomain("@PACKAGE@");
+
+my $verbose = 0;
+my $dry_run = 0;
+
+# General FIXMEs:
+
+# - implement help for the command line
+
+# - Externalize intltool rule extractor, via a config file or something
+# (i.e. don't hardcode)
+
+# - Don't remove backslashes from read macros
+
+# - take makefile names from configure.{in,ac}, since they can also be
+# named makefile (lowercase) and besides that's the way automake works
+
+# File index:
+# 1. CONSTANTS AND VARIABLES
+# 2. PARSER FUNCTIONS
+# 3. TARGET EXTRACT FUNCTIONS
+# 4. GROUP AND PROJECT CONSTRUCTION
+# 5. PROJECT MODIFICATION
+# 6. TARGET WRITER FUNCTIONS
+# 7. XML PRINTING FUNCTIONS
+# 8. XML SCANNING
+# 9. XML PROCESSING
+# 10. HELPER FUNCTIONS
+# 11. MAIN PROGRAM
+
+##########################################################################################
+#
+# BEGIN DOCUMENTATION
+#
+# Syntax:
+# ---------------------------------------------------------------------------------------
+#
+# gbf-am-parse [options] <operation> <argument>
+#
+# Operations:
+#
+# --get : argument is the project root
+# analyzes the automake project and outputs an xml representation
+#
+# --set : argument is a file (or - for stdin)
+# reads a xml file which specifies operations to be performed
+# outputs changed project groups affected by the operations
+#
+# Options:
+#
+# -v (--verbose) : output additional information during processing
+# -n (--dry-run) : when in --set operation mode don't alter any files, just
+# parse the input
+# -d (--debug) : output debugging information
+#
+# Output:
+#
+# In stdout the script outputs the xml representation of the project
+# (or the changed groups). Through stderr the script outputs errors and warnings
+# using the following format:
+#
+# DEBUG: message
+# ERROR(code): message
+# WARNING(code): message
+#
+# Error codes:
+#
+# 0 Success
+# 1-99 General errors
+# 1: Malformed project
+# 2: Invalid directory
+# 3: Cannot open output file
+# 4: Cannot open input file
+# 5: Syntax error
+# 100-199 configure.in/Makefile.am parser error
+# 100: Can't open file
+# 101: Invalid trailing backslash
+# 200-299 Extraction errors
+# 200: Invalid primary prefix
+# 300-399 Target writer errors
+# 300: Invalid operation
+# 301: Invalid operand
+# 302: Unimplemented target writer
+# 303: Group not found
+# 304: Can't write file
+# 305: Target/group already exists
+#
+# Warning codes:
+#
+# 0 Success
+# 1-99 General warnings
+# 100-199 configure.in/Makefile.am parser warning
+# 100: Adding text to not previously declared macro
+# 101: General error in configure.in (other things will not work)
+# 102: Makefile.am for a subdirectory not found
+# 200-299 Extraction warning
+# 200: General semantic error
+# 300-399 Target writer warning
+# 300: Creating an already existing macro
+# 301: Out of bounds line number
+# 302: Target types don't match while creating a new simple primary target
+#
+#
+#
+# Output format (see output.dtd):
+# ---------------------------------------------------------------------------------------
+#
+# Sample output:
+# <?xml version='1.0' encoding='ISO-8859-1' standalone='yes'?>
+# <!DOCTYPE project []>
+# <project root="/home/gustavo/tmp/gdl">
+#
+# the 'root' attribute is a full path to the root of the project
+#
+# <config>
+# <param name="source_generators">
+# <item name="glib_genmarshal" value="GLIB_GENMARSHAL"/>
+# <item name="glib_mkenums" value="GLIB_MKENUMS"/>
+# <item name="orbit_idl" value="ORBIT_IDL"/>
+# </param>
+# </config>
+#
+# the project itself can contain some 'config' parameters
+# the config is composed of 'param' elements
+# special characters in parameter values are escaped
+# \ (the backslash) -> '\\'
+# \n (newline) -> '\n'
+# \t (tab character) -> '\t'
+#
+# there are three types of parameters:
+#
+# 1) simple strings:
+# <param name="parameter" value="string"/>
+#
+# 2) lists:
+# <param name="list">
+# <item value="item 1"/>
+# <item value="item 2"/>
+# </param>
+#
+# 3) mappings:
+# <param name="mapping">
+# <item name="item1" value="value 1"/>
+# <item name="item2" value="value 2"/>
+# </param>
+#
+# Next in the project comes the root group
+# each group can have:
+# - one config section (defined as above)
+# - some targets
+# - other groups nested in
+#
+# <group name="/" id="/">
+#
+# here the attribute 'name' is a printable name
+# and 'id' is a unique name to identify the group in the whole project
+# also 'id' is the path from the project root for the group
+# here's a nested group (nested groups appear before targets but after group config
+#
+# <group name="idl" id="/idl/">
+# <config>
+# <param name="installdirs">
+# <item name="idl" value="$(datadir)/idl/libgdl-1.0"/>
+# </param>
+# </config>
+# <target name="idl" type="data" id="idl:data">
+#
+# each target has a printable 'name' a unique 'id' (within the group) and a 'type'
+# conventionally id is name:type but that is subject to change
+# the targets also can have a config section
+#
+# <config>
+# <param name="installdir" value="idl"/>
+# </config>
+#
+# after the config section, comes the target sources and dependencies
+# the difference between a source and a dependency is that plain sources are
+# files available directly, and dependencies are files which are in turn
+# generated from other targets
+#
+# <source uri="/idl/GDL.idl"/>
+# <source uri="/idl/editor-buffer.idl"/>
+# <source uri="/idl/editor-gutter.idl"/>
+# <source uri="/idl/symbol-browser.idl"/>
+# </target>
+#
+# an example of a target with dependencies:
+#
+# <target name="libgdl-1.la" type="shared_lib" id="libgdl-1.la:shared_lib">
+# <source uri="/gdl/default-icon.c"/>
+# .
+# .
+# <source uri="/gdl/gdl-recent.c"/>
+# <dependency file="/gdl/libgdlmarshal.c" target="libgdlmarshal.c:rule"/>
+# .
+# .
+# <dependency file="/gdl/libgdltypebuiltins.c" target="libgdltypebuiltins.c:rule"/>
+# </target>
+#
+# the 'uri' attribute contains a path absolute to the project root for the file
+# the 'file' is the same as 'uri' but for dependencies, and 'target' is the target
+# 'id' as explained before (the targets belong to the same group)
+#
+#
+# Internal architecture:
+# ---------------------------------------------------------------------------------------
+#
+# All data is kept in anonymous hashes to emulate a C struct
+#
+# Meta structure definitions
+#
+# +--------------
+# | project : information about the project as a whole
+# +----------------
+# | configure_in : configure_in hash (defined below)
+# | prefix : string containing the root directory of the project
+# | config : project_config hash (defined below)
+# | root_group : root group hash (defined below)
+# | all_groups : flat hash containing all the groups for faster access
+# |
+#
+# +-----------------
+# | configure_in : information regarding the parsing of the configure.in file
+# +--------------------
+# | lines : list of text lines in configure.in file
+# | other_files : hash of configure time generated files (target => source)
+# | vars : hash of AC_SUBSTed vars (varname => contents)
+# |
+#
+# +----------------
+# | project_config : project configuration
+# +-----------------
+# | source_generators : hash of defined source generators mapped to configure.in vars
+# | (source_gen_name => configure_in_variable)
+# |
+#
+# +----------------
+# | group : group (or directory for this backend) information
+# +-----------------
+# | name : string, the name of the group
+# | makefile : makefile hash (see GBF/AmFiles.pm)
+# | targets : hash of target hashes (defined below)
+# | (target_name => target_hash)
+# | config : group_config hash for this directory
+# | groups : hash of subgroups hashes
+# | changed : the group_process procedure puts this flag to 1
+# |
+#
+# +----------------
+# | group_config : group configuration
+# +-----------------
+# | subdirs : SUBDIRS macro (order is important!!)
+# | includes (*) : INCLUDES macro
+# | ldadd (*) : LDADD macro
+# | compile (*) : COMPILE macro
+# | link (*) : LINK macro
+# | options (*) : AUTOMAKE_OPTIONS macro
+# | omit_deps (*) : OMIT_DEPENDENCIES macro
+# | built_sources : BUILT_SOURCES macro (expanded)
+# | extra_dist : EXTRA_DIST macro (expanded)
+# | cleanfiles (*) : CLEANFILES macro
+# | mostlyclean (*) : MOSTLYCLEANFILES macro
+# | maintainclean (*) : MAINTAINCLEANFILES macro
+# | distclean (*) : DISTCLEANFILES macro
+# | install_exec_local(*) : install-exec-local rule actions
+# | install_data_local(*) : install-data-local rule actions
+# | dist_hook (*) : dist-hook rule actions
+# | other_vars : hash of unused remaining Makefile.am macros after target
+# | extraction (macro_name => macro_contents)
+# | installdirs : hash containing user defined installation prefixes
+# | (prefix => installation_dir)
+# |
+#
+# +----------------
+# | target : target information
+# +-----------------
+# | id : id of the target (how it's identified in the group's hash
+# | name : string, name of the target
+# | type : type of the target
+# | sources : list of the source files which this target depends on
+# | dependencies : list of other targets this target depends on
+# | built_files : list of files presumably this target generates
+# | config : target_config hash (defined below)
+# |
+#
+# +----------------
+# | target_config : extra target configuration (these are all optional and
+# | dependent of the target type)
+# +-----------------
+# | ldflags (*) : for a primary target, target_LDFLAGS macro contents
+# | ldadd (*) : for a primary target, target_LDADD macro contents
+# | libadd (*) : for a primary target, target_LIBADD macro contents
+# | explicit_deps (*) : for a primary target, target_DEPENDENCIES macro contents
+# | actions (*) : rule actions if the target is derived from a makefile rule
+# | installdir : installation prefix (or noinst, check, EXTRA)
+# |
+#
+# (*) These fields are optional, they can be empty or undef
+#
+#
+# END DOCUMENTATION ------------------
+#
+##########################################################################################
+
+
+
+######################################################################
+#### 1. CONSTANTS AND VARIABLES ####################################
+######################################################################
+
+
+# These files are included automatically by automake, so they should be sources
+# of the "extra" target in the root group
+# Makefile.am's and configure.in should be only accessible through the
+# configuration controls of the backend
+
+my @auto_files = qw ( ChangeLog README INSTALL COPYING AUTHORS NEWS );
+
+my %am_valid_prefixes = ( PROGRAMS => [ qw ( bin sbin libexec pkglib check) ],
+ LIBRARIES => [ qw ( lib pkglib ) ],
+ LTLIBRARIES => [ qw ( lib pkglib ) ],
+ SCRIPTS => [ qw ( bin sbin libexec pkgdata check) ],
+ HEADERS => [ qw ( include oldinclude pkginclude ) ],
+ DATA => [ qw ( data sysconf sharedstate localstate
+ pkgdata ) ],
+ LISP => [ qw ( lisp ) ],
+ TEXINFOS => [ qw ( info ) ],
+ MANS => [ qw ( man ) ] );
+
+my %automake_types = ( PROGRAMS => "program",
+ LIBRARIES => "static_lib",
+ LTLIBRARIES => "shared_lib",
+ MANS => "man",
+ DATA => "data",
+ SCRIPTS => "script",
+ TEXINFOS => "info",
+ LISP => "lisp",
+ HEADERS => "headers",
+ JAVA => "java",
+ PYTHON => "python" );
+
+my $PRIMARY_MATCHER = "^(\\w+)_(" . join ('|', keys %automake_types) . ")\\z";
+
+my %target_writers = ( program => \&compiled_primary_target_writer,
+ static_lib => \&compiled_primary_target_writer,
+ shared_lib => \&compiled_primary_target_writer,
+ man => \&unimplemented_writer,
+ data => \&simple_primary_target_writer,
+ script => \&simple_primary_target_writer,
+ info => \&unimplemented_writer,
+ lisp => \&unimplemented_writer,
+ headers => \&simple_primary_target_writer,
+ java => \&simple_primary_target_writer,
+ python => \&simple_primary_target_writer,
+ generic_rule => \&unimplemented_writer,
+ extra => \&simple_extra_dist_target_writer,
+ configure_generated_file
+ => \&unimplemented_writer,
+ orbit_idl => \&unimplemented_writer,
+ glib_mkenums => \&unimplemented_writer,
+ glib_genmarshal => \&unimplemented_writer,
+ intltool_rule => \&unimplemented_writer );
+
+
+# Other special variables
+
+my %intltool_rules = ( INTLTOOL_DESKTOP_RULE => ".desktop",
+ INTLTOOL_DIRECTORY_RULE => ".directory",
+ INTLTOOL_KEYS_RULE => ".keys",
+ INTLTOOL_OAF_RULE => ".oaf",
+ INTLTOOL_PONG_RULE => ".pong",
+ INTLTOOL_SERVER_RULE => ".server",
+ INTLTOOL_SHEET_RULE => ".sheet",
+ INTLTOOL_SOUNDLIST_RULE => ".soundlist",
+ INTLTOOL_UI_RULE => ".ui",
+ INTLTOOL_XML_RULE => ".xml",
+ INTLTOOL_CAVES_RULE => ".caves" );
+
+
+######################################################################
+#### 2. PARSER FUNCTIONS ###########################################
+######################################################################
+
+
+
+######################################################################
+#### 3. TARGET EXTRACT FUNCTIONS ###################################
+######################################################################
+
+# (target extraction from makefile.am contents)
+
+sub extract_project_config
+{
+ my $project = $_[0];
+
+ $project->{config} = {};
+ use Data::Dumper;
+ ## print Dumper($project->{configure_in});
+
+ # Package, version and URL
+ my ($package_name, $package_version, $package_url);
+
+ if ($project->{configure_in}{contents} =~ /AC_INIT\(([^\,\)]+)\)/ms)
+ {
+ $package_name = $1;
+ $package_name =~ s/^[\s\[]*//s;
+ $package_name =~ s/[\s\]]*$//s;
+ $project->{config}->{package_name} = $package_name;
+ }
+ elsif ($project->{configure_in}{contents} =~ /AC_INIT\(([^\,\)]+),([^\,\)]+)\)/ms)
+ {
+ $package_name = $1;
+ $package_version = $2;
+ $package_name =~ s/^[\s\[]*//s;
+ $package_name =~ s/[\s\]]*$//s;
+ $package_version =~ s/^[\s\[]*//s;
+ $package_version =~ s/[\s\]]*$//s;
+ $project->{config}->{package_name} = $package_name;
+ $project->{config}->{package_version} = $package_version;
+ }
+ elsif ($project->{configure_in}{contents} =~ /AC_INIT\(([^\,\)]+),([^\,\)]+),([^\,\)]+)\)/ms)
+ {
+ $package_name = $1;
+ $package_version = $2;
+ $package_url = $3;
+ $package_name =~ s/^[\s\[]*//s;
+ $package_name =~ s/[\s\]]*$//s;
+ $package_version =~ s/^[\s\[]*//s;
+ $package_version =~ s/[\s\]]*$//s;
+ $package_url =~ s/^[\s\[]*//s;
+ $package_url =~ s/[\s\]]*$//s;
+ $project->{config}->{package_name} = $package_name;
+ $project->{config}->{package_version} = $package_version;
+ $project->{config}->{package_url} = $package_url;
+ }
+
+ # Variables.
+ my %vars = %{$project->{configure_in}{vars}};
+ my @vars_order = @{$project->{configure_in}{vars_order}};
+ $project->{config}->{variables} = \%vars;
+ $project->{config}->{variables_order} = \ vars_order;
+
+ # Substitutions.
+ my @substitutions = $project->{configure_in}{contents} =~ /AC_SUBST\(([^\)]+)\)/sg;
+ $project->{config}->{substitutions} = \ substitutions;
+
+ # Get known source generators from variables
+ my %source_generators;
+ foreach my $var (keys %vars) {
+ $_ = $vars{$var};
+ if (/\$PKG_CONFIG.+--variable=orbit_idl/) {
+ $source_generators{orbit_idl} = $var;
+ &debug ("Found orbit-idl compiler in configure.in var $var");
+ }
+ elsif (/\$PKG_CONFIG.+--variable=glib_mkenums/) {
+ $source_generators{glib_mkenums} = $var;
+ &debug ("Found glib-mkenums in configure.in var $var");
+ }
+ elsif (/\$PKG_CONFIG.+--variable=glib_genmarshal/) {
+ $source_generators{glib_genmarshal} = $var;
+ &debug ("Found glib-genmarshal in configure.in var $var");
+ };
+ };
+ $project->{config}->{source_generators} = \%source_generators;
+
+ # Extract pkg-config packages
+ my %pkgconfig_modules = $project->{configure_in}{contents}
+ =~ /PKG_CHECK_MODULES\([\s\[]*([^\,\)\]]+)[\s\]]*\,(.+?)\)/sg;
+ foreach my $one_module (keys(%pkgconfig_modules)) {
+ my $line = $pkgconfig_modules{$one_module};
+ my $packages = "";
+ my $yes_code = "";
+ my $no_code = "";
+ if ($line =~ /^[\s\]]*([^\,\]]+)[\s\]]*,([^\,]+),([^\,]+)$/s) {
+ $packages = $1;
+ $yes_code = $2;
+ $no_code = $3;
+ $packages =~ s/^[\s\[]*//s;
+ $packages =~ s/[\s\]]*$//s;
+ } elsif ($line =~ /^[\s\]]*([^\,\]]+)[\s\]]*,([^\,]+)$/s) {
+ $packages = $1;
+ $yes_code = $2;
+ $packages =~ s/^[\s\[]*//s;
+ $packages =~ s/[\s\]]*$//s;
+ } else {
+ $packages = $line;
+ $packages =~ s/^[\s\[]*//s;
+ $packages =~ s/[\s\]]*$//s;
+ }
+ $packages =~ s/\s+/ /sg;
+ $packages =~ s/^\s+//sg;
+ $packages =~ s/s+$//sg;
+ $packages =~ s/^\[//sg;
+ $packages =~ s/\]$//sg;
+ $yes_code =~ s/^\s+//sg;
+ $yes_code =~ s/s+$//sg;
+ $yes_code =~ s/^\[//sg;
+ $yes_code =~ s/\]$//sg;
+ $no_code =~ s/^\s+//sg;
+ $no_code =~ s/s+$//sg;
+ $no_code =~ s/^\[//sg;
+ $no_code =~ s/\]$//sg;
+
+ $packages =~ s/ >= /_\$\$_>=_\$\$_/g;
+ $packages =~ s/ <= /_\$\$_<=_\$\$_/g;
+ $packages =~ s/ == /_\$\$_==_\$\$_/g;
+ $packages =~ s/ = /_\$\$_=_\$\$_/g;
+
+ my @pkgs = split(/\s+/, $packages);
+ my @tmp;
+ foreach my $pkg (@pkgs) {
+ $pkg =~ s/_\$\$_>=_\$\$_/ >= /g;
+ $pkg =~ s/_\$\$_<=_\$\$_/ <= /g;
+ $pkg =~ s/_\$\$_==_\$\$_/ == /g;
+ $pkg =~ s/_\$\$_=_\$\$_/ = /g;
+ push (@tmp, $pkg);
+ }
+ @pkgs = @tmp;
+
+ my %info;
+ $info{'packages'} = join (", ", @pkgs);
+ $info{'action-if'} = $yes_code;
+ $info{'action-not'} = $no_code;
+
+ $project->{config}->{"pkg_check_modules_${one_module}"} = \%info;
+ if (!defined($project->{config}->{"pkg_check_modules"})) {
+ $project->{config}->{"pkg_check_modules"} = "$one_module";
+ } else {
+ $project->{config}->{"pkg_check_modules"} .= ", $one_module";
+ }
+ }
+}
+
+sub extract_group_config
+{
+ my ($group, $project) = @_;
+
+ my %macros = %{$group->{makefile}{macros}};
+ my %rules = %{$group->{makefile}{rules}};
+ my $config = $group->{config};
+
+ $config->{includes} = &use_macro ($macros{INCLUDES});
+ $config->{ldadd} = &use_macro ($macros{LDADD});
+ $config->{libadd} = &use_macro ($macros{LIBADD});
+ $config->{compile} = &use_macro ($macros{COMPILE});
+ $config->{link} = &use_macro ($macros{LINK});
+ $config->{amcflags} = &use_macro($macros{AM_CFLAGS});
+ $config->{amcppflags} = &use_macro($macros{AM_CPPFLAGS});
+ $config->{amcxxflags} = &use_macro($macros{AM_CXXFLAGS});
+ $config->{amgcjflags} = &use_macro($macros{AM_GCJFLAGS});
+ $config->{amjavaflags} = &use_macro($macros{AM_JAVAFLAGS});
+ $config->{amfflags} = &use_macro($macros{AM_FFLAGS});
+
+ # FIXME: extract inherited from autoconf: CC, CFLAGS, CPPFLAGS, DEFS, LDFLAGS,
+ # LIBS, CXX, CXXFLAGS, CXXCOMPILE, CXXLINK, JAVAC
+ # FIXME: other automake flags (Fortran, etc.)
+ # FIXME: what is ELCFILES?
+
+ $config->{built_sources} = &empty_if_undef (&use_macro ($macros{BUILT_SOURCES}));
+ $config->{extra_dist} = &empty_if_undef (&use_macro ($macros{EXTRA_DIST}));
+ $config->{subdirs} = trim(join(' ', (
+ &empty_if_undef (&use_macro ($macros{SUBDIRS})),
+ &empty_if_undef (&use_macro ($macros{DIST_SUBDIRS})),
+ &empty_if_undef (&use_macro ($macros{SRC_SUBDIRS})),
+ )));
+
+ $config->{options} = &use_macro ($macros{AUTOMAKE_OPTIONS});
+ $config->{omit_deps} = &use_macro ($macros{OMIT_DEPENDENCIES});
+ $config->{cleanfiles} = &use_macro ($macros{CLEANFILES});
+ $config->{mostlyclean} = &use_macro ($macros{MOSTLYCLEANFILES});
+ $config->{distclean} = &use_macro ($macros{DISTCLEANFILES});
+ $config->{maintainclean} = &use_macro ($macros{MAINTAINCLEANFILES});
+
+ (undef, $config->{install_data_local}) = &use_rule ($rules{'install-data-local'});
+ (undef, $config->{install_exec_local}) = &use_rule ($rules{'install-exec-local'});
+ (undef, $config->{dist_hook}) = &use_rule ($rules{'dist-hook'});
+
+ $config->{other_vars} = {};
+ $config->{installdirs} = {};
+
+ my $top_srcdir = "$group->{prefix}";
+ $top_srcdir =~ s/\/([^\/]+)/\/\.\./g; # replace all directory names by ..
+ $top_srcdir = substr $top_srcdir, 1, -1; # strip leading slash
+
+ # Add special macros for later expanding
+ $group->{makefile}{macros}{srcdir} = { contents => ".",
+ atline => 0,
+ used => 1 };
+ $group->{makefile}{macros}{top_srcdir} = { contents => $top_srcdir,
+ atline => 0,
+ used => 1 };
+}
+
+sub check_primary_prefix
+{
+ my ($prefix, $primary, $group) = @_;
+
+ if ($prefix eq "EXTRA" || $prefix eq "noinst" || $prefix eq "check") {
+ return $prefix;
+ };
+
+ my %macros = %{$group->{makefile}{macros}};
+
+ # get user supplied prefix (if there is one)
+ my @user_dir = grep (($_ eq $prefix . "dir"), keys %macros);
+
+ if (grep (($_ eq $prefix), @{$am_valid_prefixes{$primary}}) || @user_dir) {
+ if (@user_dir) {
+ # Save the installation dir in the group configuration
+ if (!defined ($group->{config}{installdirs}{$prefix})) {
+ $group->{config}{installdirs}{$prefix} =
+ &use_macro ($macros{$user_dir[0]});
+ };
+ };
+ return $prefix;
+ }
+ else {
+ ## Some projects have install dirs defined in configure and
+ ## therefore they are not found in the Makefile.am and is not
+ ## necessarily an error condition.
+ ## &report_error (200, "Invalid prefix '$prefix' for primary '$primary'");
+ ## return "";
+
+ # Save the installation dir in the group configuration
+ if (!defined ($group->{config}{installdirs}{$prefix})) {
+ $group->{config}{installdirs}{$prefix} = "";
+ };
+ return $prefix;
+ };
+}
+
+# Function extract_standard_targets
+
+# Extracts standard automake targets from parsed Makefile.am
+
+sub extract_standard_targets
+{
+ my $group = $_[0];
+ my $makefile = $group->{makefile};
+ my %macros = %{$makefile->{macros}};
+ my %rules = %{$makefile->{rules}};
+
+ # Search for primary variables
+ MACRO: foreach my $macro_name (keys %macros) {
+ if ($macros{$macro_name}{used}) {
+ next MACRO;
+ };
+
+ my ($prefix, $primary);
+
+ if ($macro_name =~ $PRIMARY_MATCHER) {
+ $prefix = $1;
+ $primary = $2;
+ } else {
+ # we don't handle any other than primary automake types here
+ next MACRO;
+ };
+
+ my $install_dir = &check_primary_prefix ($prefix, $primary, $group);
+ my $type = $automake_types{$primary};
+
+ my $tmp_value = &expand_one_var (&use_macro ($macros{$macro_name}), %macros);
+ my @split_macro = split /\s+/, &trim ($tmp_value);
+
+ # Handle programs, scripts and libraries
+ if ($primary eq "PROGRAMS" || $primary eq "LIBRARIES" ||
+ $primary eq "LTLIBRARIES" || $primary eq "SCRIPTS") {
+
+ foreach my $target (@split_macro) {
+ my %new_target;
+ my $canonical = &canonicalize_name ($target);
+
+ # Fill in the new target info
+ %new_target = ( id => "$target:$type",
+ name => $target,
+ type => $type,
+ sources => [],
+ dependencies => [],
+ built_files => [ $target ],
+ config => { installdir => $install_dir } );
+
+ my $sources = &use_macro ($macros{$canonical . "_SOURCES"});
+ if (defined ($sources)) {
+ $new_target{sources} = [split /\s+/, $sources];
+ $sources = &use_macro ($macros{"EXTRA_" . $canonical . "_SOURCES"});
+ if (defined $sources) {
+ push @{$new_target{sources}}, split /\s+/, $sources;
+ };
+ } else {
+ # Default automake source
+ $new_target{sources} = ["$target.c", ];
+ };
+
+ # LIBADD and LDADD parameters
+ $new_target{config}{ldadd} =
+ &empty_if_undef (&use_macro ($macros{$canonical . "_LDADD"}));
+ $new_target{config}{libadd} =
+ &empty_if_undef (&use_macro ($macros{$canonical . "_LIBADD"}));
+
+ $new_target{config}{ldflags} =
+ &use_macro ($macros{$canonical . "_LDFLAGS"});
+ $new_target{config}{explicit_deps} =
+ &use_macro ($macros{$canonical . "_DEPENDENCIES"});
+ $new_target{config}{cflags} =
+ &use_macro ($macros{$canonical . "_CFLAGS"});
+ $new_target{config}{cppflags} =
+ &use_macro ($macros{$canonical . "_CPPFLAGS"});
+ $new_target{config}{cxxflags} =
+ &use_macro ($macros{$canonical . "_CXXFLAGS"});
+ $new_target{config}{gcjflags} =
+ &use_macro ($macros{$canonical . "_GCJFLAGS"});
+ $new_target{config}{fflags} =
+ &use_macro ($macros{$canonical . "_FFLAGS"});
+ # If there is a rule for $(canonical_OBJECTS), then it should be
+ # treated as defining extra dependencies for the target
+ my ($extra_deps, $extra_actions) =
+ &use_rule ($rules{"\$(${canonical}_OBJECTS)"});
+ if (defined ($extra_deps)) {
+ $new_target{config}{explicit_deps} .= $extra_deps;
+ if ($extra_actions ne "") {
+ &report_warning (200, gettext("Actions defined for \$(${canonical}_OBJECTS), and this should be left to automake alone"));
+ };
+ };
+
+ # Add the target to the group
+ $group->{targets}{$new_target{id}} = \%new_target;
+ };
+ }
+ elsif ($primary eq "SCRIPTS" || $primary eq "DATA" || $primary eq "HEADERS" ||
+ $primary eq "PYTHON" || $primary eq "JAVA") {
+ # for SCRIPTS, HEADERS, JAVA, PYTHON and DATA we generate a single
+ # target whose sources are all the files mentioned in the macro
+ # definition.
+ # FIXME: data files are not included in dist by default,
+ # whereas others are so, we need to make the distinction here
+ my %new_target;
+
+ my $target_name = "$prefix";
+
+ %new_target = ( id => "$prefix:$type",
+ name => $target_name,
+ type => $type,
+ sources => \ split_macro,
+ dependencies => [],
+ built_files => [],
+ config => { installdir => $install_dir } );
+
+ $group->{targets}{$new_target{id}} = \%new_target;
+ };
+
+ # FIXME: handle JAVA (they aren't included in dist), LISP
+ # FIXME: handle TEXINFOS (and dependencies, which are texi_TEXINFOS)
+ # FIXME: handle MANS (not included in dist)
+ };
+}
+
+# FIXME: maybe we should split this function in one or more subs, one for
+# intltool rules and other for source generators
+sub extract_other_targets
+{
+ my ($group, %source_generators) = @_;
+
+ # Look for specific, known, rules
+ my @extra = @{$group->{makefile}{extra}};
+ my %rules = %{$group->{makefile}{rules}};
+ my %macros = %{$group->{makefile}{macros}};
+
+ # Look for rules containing known source generators
+ RULE:
+ for my $rule (keys %rules) {
+ if ($rules{$rule}{used}) {
+ next RULE;
+ };
+
+ my %new_target;
+
+ %new_target = ( id => "$rule:rule",
+ name => rule_name ($rule),
+ type => "",
+ sources => [],
+ dependencies => [],
+ built_files => [],
+ config => { installdir => "" } );
+
+ $_ = $rules{$rule}{actions};
+
+ if (/orbit-idl/ || (defined ($source_generators{orbit_idl}) &&
+ /(\$\(|\@)$source_generators{orbit_idl}(\)|\@)/)) {
+ &debug ("Found orbit-idl rule $rule");
+ $new_target{type} = "orbit_idl";
+ # FIXME: we should actually get the filename given to orbit-idl and
+ # append -common.c, -skels.c, -stubs.c and .h to it
+ # For now we assume the rule itself contains the generated files
+ $new_target{built_files} = [ $rule ];
+ }
+ elsif (/glib-mkenums/ || (defined ($source_generators{glib_mkenums}) &&
+ /(\$\(|\@)$source_generators{glib_mkenums}(\)|\@)/)) {
+ &debug ("Found glib-mkenums rule $rule");
+ $new_target{type} = "glib_mkenums";
+ $new_target{built_files} = [ $rule ];
+ }
+ elsif (/glib-genmarshal/ ||
+ (defined ($source_generators{glib_genmarshal}) &&
+ /(\$\(|\@)$source_generators{glib_genmarshal}(\)|\@)/)) {
+ &debug ("Found glib-genmarshal rule $rule");
+ $new_target{type} = "glib_genmarshal";
+ $new_target{built_files} = [ $rule ];
+ }
+ else {
+ # Next if we didn't match any known generator
+ next RULE;
+ };
+
+ # If we got here, we matched a generator, and we already have its type set
+ my ($deps, $actions) = &use_rule ($rules{$rule});
+
+ $new_target{sources} = [ split /\s+/, $deps ];
+ $new_target{config}{actions} = $actions;
+
+ $group->{targets}{$new_target{id}} = \%new_target;
+ };
+
+ # Look for INTLTOOL_*_RULES
+ foreach my $intltool_rule (keys %intltool_rules) {
+ if (grep /^\ $intltool_rule\@$/, @extra) {
+ &debug ("\ $intltool_rule\@ found");
+
+ my %new_target;
+
+ my $ext = $intltool_rules{$intltool_rule};
+
+ # FIXME: we make a strong assumption here: all .ext files are already
+ # generated as sources of other targets, and this may not be true, since
+ # when we get here, there are still makefile rules unanalyzed
+ my @built_files = &get_sources_by_extension ($group, $ext);
+ my @sources = map "$_.in", @built_files;
+
+ %new_target = ( id => "$ext:intltool_rule",
+ name => "$ext files translation",
+ type => "intltool_rule",
+ sources => \ sources,
+ dependencies => [],
+ built_files => \ built_files,
+ config => { installdir => "" } );
+
+ $group->{targets}{$new_target{id}} = \%new_target;
+ };
+ };
+}
+
+sub rule_name
+{
+ my $rule = $_[0];
+
+ $rule =~ tr/a-zA-Z _//cd;
+
+ return "$rule";
+}
+
+sub make_targets_from_rules
+{
+ my $group = $_[0];
+ my %rules = %{$group->{makefile}{rules}};
+ my %macros = %{$group->{makefile}{macros}};
+
+ RULE:
+ foreach my $rule (keys %rules) {
+ my %new_target;
+
+ if ($rules{$rule}{used}) {
+ next RULE;
+ };
+
+ my ($deps, $actions) = &use_rule ($rules{$rule});
+
+ %new_target = ( id => "$rule:rule",
+ name => rule_name ($rule),
+ type => "generic_rule",
+ sources => [ split /\s+/, $deps ],
+ dependencies => [],
+ built_files => [ $rule ],
+ config => { actions => $actions,
+ installdir => "" } );
+
+ $group->{targets}{$new_target{id}} = \%new_target;
+ };
+}
+
+sub extract_remaining_macros
+{
+ my $group = $_[0];
+
+ my %macros = %{$group->{makefile}{macros}};
+ my %other_vars;
+
+ for my $macro (keys %macros) {
+ if (! $macros{$macro}{used}) {
+ $other_vars{$macro} = &use_macro ($macros{$macro});
+ };
+ };
+
+ $group->{config}{other_vars} = \%other_vars;
+}
+
+sub expand_variables
+{
+ my ($group, $only_targets) = @_;
+
+ my %macros = %{$group->{makefile}{macros}};
+ my %targets = %{$group->{targets}};
+
+ for my $target (keys %targets) {
+ # Expand sources
+ my $sources = join ' ', @{$targets{$target}{sources}};
+ $sources = &expand_one_var ($sources, %macros);
+ # Remove any reference to the Makefile itself
+ $sources =~ s/\s+Makefile\s+//g;
+ $targets{$target}{sources} = [ split '\s+', &trim ($sources) ];
+
+ # Expand built_files
+ my $built_files = join ' ', @{$targets{$target}{built_files}};
+ $built_files = &expand_one_var ($built_files, %macros);
+ $targets{$target}{built_files} = [ split '\s+', &trim ($built_files) ];
+ };
+
+ if (! $only_targets) {
+ # Expand BUILT_SOURCES
+ $group->{config}{built_sources} =
+ &trim (&expand_one_var ($group->{config}{built_sources}, %macros));
+ &debug ("BUILT_SOURCES = $group->{config}{built_sources}");
+
+ # Expand EXTRA_DIST
+ $group->{config}{extra_dist} =
+ &trim (&expand_one_var ($group->{config}{extra_dist}, %macros));
+
+ # Expand subdirs
+ $group->{config}{subdirs} =
+ &trim (&expand_one_var ($group->{config}{subdirs}, %macros));
+ };
+}
+
+sub reduce_sources
+{
+ my $group = $_[0];
+
+ my %targets = %{$group->{targets}};
+
+ # FIXME: also remove all sources which don't exist as files
+ foreach my $target (values %targets) {
+ my @targets_found;
+ my $sources = join ' ', @{$target->{sources}};
+
+ ($sources, @targets_found) = &remove_files_from_built_files ($sources, $group);
+
+ $target->{sources} = [ split /\s+/, $sources ];
+ if (@targets_found) {
+ # FIXME: there must be a more direct way to do this
+ my @dependencies = @{$target->{dependencies}};
+ push @dependencies, @targets_found;
+ $target->{dependencies} = \ dependencies;
+ };
+ };
+}
+
+
+######################################################################
+#### 4. GROUP AND PROJECT CONSTRUCTION #############################
+######################################################################
+
+sub group_process
+{
+ my $group = $_[0];
+ my $project = $group->{project};
+
+ # unuse all macros and rules
+ &am_file_reset ($group->{makefile});
+
+ # Standard automake targets
+ &extract_group_config ($group, $project);
+ &extract_standard_targets ($group);
+
+ # We need to make a first expansion here so the intltool rules
+ # gets sources correctly
+ &expand_variables ($group, 1);
+
+ # Extract known target types from rules
+ &extract_other_targets ($group, %{$project->{config}->{source_generators}});
+
+ # Get remaining rules and get them as generic rules in the group's targets
+ &make_targets_from_rules ($group);
+
+ # Get remaining macros and put them in the group's config
+ &extract_remaining_macros ($group);
+
+ # Create a target for each file generated at configure time
+ my $relative_path = substr "$group->{prefix}", 1;
+ my %other_files = %{$project->{configure_in}{other_files}};
+ foreach my $file (keys %other_files) {
+ # Strip relative path from generated file and source
+ if ($file =~ /^$relative_path[^\/]+$/) {
+ my $source = $other_files{$file};
+ $source =~ s/^$relative_path//;
+ $file =~ s/^$relative_path//;
+ my %new_target = ( id => "$file:configure_generated_file",
+ name => $file,
+ type => "configure_generated_file",
+ sources => [ $source ],
+ dependencies => [],
+ built_files => [ $file ],
+ config => { installdir => "" } );
+ $group->{targets}{$new_target{id}} = \%new_target;
+ };
+ };
+
+ # Expand variables in target sources, built_files, EXTRA_DIST and BUILT_SOURCES
+ &expand_variables ($group, 0);
+
+ # Create the extra target
+ my @sources = split /\s+/, &remove_files_from_sources
+ ($group->{config}{extra_dist}, $group);
+
+ if ($group->{name} eq "/") {
+ foreach my $auto_file (@auto_files) {
+ my $duplicate = 0;
+ foreach my $source (@sources) {
+ if ($source eq $auto_file) {
+ $duplicate = 1;
+ last;
+ };
+ };
+ if (!$duplicate) {
+ push @sources, $auto_file;
+ };
+ };
+ };
+
+ if (@sources) {
+ $group->{targets}{"other:extra"} = { id => "other:extra",
+ name => "other files",
+ type => "extra",
+ sources => \ sources,
+ dependencies => [],
+ built_files => [],
+ config => { installdir => "" } };
+ };
+
+ # Remove files from sources when those files are generated by
+ # other targets
+ &reduce_sources ($group);
+
+ # FIXME: since we are maybe called for reprocess of the group
+ # after a modification, we need to check if the user added/deleted
+ # any subgroups and act accordingly. Some of that is currently
+ # done in create_group
+
+ $group->{changed} = 1;
+}
+
+
+sub create_group
+{
+ my ($prefix, $name, $project) = @_;
+
+ my $group = { name => $name,
+ targets => {},
+ config => {
+ installdirs => {} },
+ groups => {},
+ prefix => $prefix,
+ project => $project };
+
+ my $full_prefix = $project->{prefix} . $prefix;
+ $group->{makefile} = &parse_am_file ("${full_prefix}Makefile.am");
+
+ if (! $group->{makefile}) {
+ return undef;
+ };
+
+ &group_process ($group);
+
+ # Recurse
+ my $subdirs = $group->{config}{subdirs};
+ if ($subdirs) {
+ my @subgroups = split /\s+/, $subdirs;
+ foreach my $subgroup (@subgroups) {
+ if ($subgroup ne ".") {
+ &debug ("${full_prefix}: Recursing into '$subgroup'");
+ my $makefile = "${full_prefix}$subgroup/Makefile.am";
+ if (-f $makefile ) {
+ my $new_prefix = $prefix ne "" ? "$prefix$subgroup/" : $subgroup;
+ $group->{groups}{$subgroup} = &create_group ($new_prefix,
+ "$subgroup",
+ $project);
+ } else {
+ &report_warning (102, gettext("file $makefile doesn't exist"));
+ }
+ };
+ };
+ };
+
+ return $group;
+}
+
+###
+# project_update_all_groups (project)
+# regenerates the flat hash which contains all groups for fast lookup
+sub project_update_all_groups
+{
+ my $project = shift;
+
+ # create flat groups hash
+ my %flat_hash = ();
+ my @helper_list = $project->{root_group};
+ while (@helper_list) {
+ my $current_group = shift @helper_list;
+ push @helper_list, values %{$current_group->{groups}};
+ $flat_hash{$current_group->{prefix}} = $current_group;
+ };
+ $project->{all_groups} = \%flat_hash;
+}
+
+sub process_project
+{
+ my $project_dir = $_[0];
+
+ &debug ("Using $project_dir as the project root");
+ if ( ! -f "$project_dir/configure.in" && ! -f "$project_dir/configure.ac" ) {
+ &report_error (1, gettext("Root directory doesn't look like the root of an " .
+ "automake package"));
+ return undef;
+ };
+
+ my $project = { prefix => $project_dir,
+ config => {},
+ configure_in => {},
+ root_group => {} };
+
+ # Parse configure.in
+ $project->{configure_in} = &parse_configure_in ($project_dir);
+ # and extract useful information
+ &extract_project_config ($project);
+
+ # Recursively parse the Makefile.am files
+ $project->{root_group} = &create_group ("/", "/", $project);
+
+ &project_update_all_groups ($project);
+
+ return $project;
+};
+
+
+######################################################################
+#### 5. PROJECT MODIFICATION #######################################
+######################################################################
+
+###
+# project_reset_changed (project)
+# reset groups' changed flag
+sub project_reset_changed
+{
+ my $project = shift;
+ foreach my $group (values %{$project->{all_groups}}) {
+ $group->{changed} = 0;
+ }
+}
+
+###
+# project_operate (project, operations)
+# perform indicated operations on project
+# returns: true if the project changed
+sub project_operate
+{
+ my ($project, $ops) = @_;
+ my $project_dirty = 0;
+
+ OP:
+ foreach my $op (@$ops) {
+ &debug ("($op->{op}, $op->{group_name}, ".
+ "$op->{target_name}, $op->{operand})");
+ my ($group, $target, $operand, $group_name, $result);
+
+ if ($op->{op} eq "add_group" || $op->{op} eq "remove_group") {
+ $operand = $op->{group_name};
+ $group_name = $operand;
+ $group_name =~ s/[^\/]+\/\z//;
+ &debug ("Adding a group $operand on group $group_name");
+ } else {
+ # Get the group from the project
+ $group_name = $op->{group_name};
+ }
+
+ # if no group name is defined, it is probably a project op.
+ if ($group_name eq "") {
+ if (!defined($operand)) {
+ $operand = $op->{operand};
+ }
+ $result = &project_op_handler ($project, $op->{op}, $operand);
+ $project_dirty = 1;
+ next OP;
+ }
+
+ # else it is group op.
+ $group = $project->{all_groups}{$group_name};
+
+ if (!$group) {
+ &report_error (303, gettext("The group $group_name doesn't exist"));
+ next OP;
+ }
+ &debug ("Using group $group->{name}");
+
+ # If the operation is on a group, do it now
+ ## if ($op->{op} =~ /group/) {
+ if ($op->{target_name} eq "") {
+ if (!defined($operand)) {
+ $operand = $op->{operand};
+ }
+ $result = &group_op_handler ($project, $group, $op->{op}, $operand);
+ $project_dirty = 1;
+ next OP;
+ }
+
+ # If not, continue with the parameters preparation
+ $operand = $op->{operand};
+ $target = $op->{target_name};
+ my $target_type = $operand;
+ if ($op->{op} ne "add_target" && $op->{op} !~ /group/) {
+ # Get the target hash from the group
+ $target = $group->{targets}{$target};
+ if (! defined ($target)) {
+ &report_error (304, gettext("The target $op->{target_name} doesn't exist"));
+ next OP;
+ }
+ $target_type = $target->{type};
+ }
+
+ # Get the target writer
+ my $writer = $target_writers{$target_type};
+ $result = &$writer ($project, $group, $op->{op}, $target, $operand);
+ if ($result == 0) {
+ $project_dirty = 1;
+ }
+ }
+
+ if ($project_dirty) {
+ # update flat hash first
+ &project_update_all_groups ($project);
+
+ # write configure.in
+ if ($project->{configure_in}{dirty}) {
+ output_configure_in $project->{configure_in};
+ $project->{configure_in} =
+ &parse_configure_in_buffer ($project->{configure_in}{filename},
+ $project->{configure_in}{contents});
+ &extract_project_config ($project);
+ # FIXME: possibly more reprocess configure.in information
+ };
+
+ foreach my $group (values %{$project->{all_groups}}) {
+ if ($group->{makefile}{dirty}) {
+ my $makefile = $group->{makefile};
+ &debug ("$makefile->{filename} is dirty");
+
+ # output modified makefile.am if not running in test mode
+ &output_am_file ($makefile, $makefile->{filename}) unless ($dry_run);
+
+ # reprocess group
+ &group_process ($group);
+
+ $makefile->{dirty} = 0;
+ };
+ };
+ };
+
+ return $project_dirty;
+}
+
+
+######################################################################
+#### 6. TARGET WRITER FUNCTIONS ####################################
+######################################################################
+
+sub group_op_handler
+{
+ my ($project, $group, $op, $operand) = @_;
+ my $makefile = $group->{makefile};
+
+ # group is the group hash in which to do the operation
+ # op is: "add_group" or "remove_group"
+ # operand is full group name for add_group, and ignored for remove_group
+
+ if ($op eq "add_group") {
+ my @components = split '/', $operand;
+ my $group_name = "";
+ $group_name = pop @components while ($group_name eq "");
+
+ # check that the group doesn't exist already
+ if (defined $group->{groups}{$group_name}) {
+ return &report_error (305, gettext("Group $operand already exists"));
+ };
+
+ # create the directory and an empty Makefile.am
+ my $local_new_dir = $group->{prefix} . $group_name;
+ my $new_dir = $project->{prefix} . $local_new_dir;
+
+ unless (-d $new_dir) {
+ mkdir $new_dir || return &report_error (304, gettext("Can't mkdir $new_dir"));
+ };
+ unless (-f "$new_dir/Makefile.am") {
+ if (open (NEWFILE, ">$new_dir/Makefile.am")) {
+ print NEWFILE "## File created by the gnome-build tools\n\n\n";
+ close NEWFILE;
+ } else {
+ return &report_error (304, gettext("Can't write $new_dir/Makefile.am"));
+ };
+ };
+
+ # add new directory to the SUBDIRS macro in the parent group
+ ¯o_append_text ($makefile, "SUBDIRS", $group_name);
+
+ # create the internal representation of the group
+ $group->{groups}{$group_name} = &create_group ($operand, "$group_name", $project);
+
+ # add Makefile to list of configure generated files
+ $local_new_dir =~ s/^\///;
+ edit_config_files $project->{configure_in}, "add", "$local_new_dir/Makefile";
+ }
+ elsif ($op eq "remove_group") {
+ my @components = split '/', $operand;
+ my $group_name = "";
+
+ $group_name = pop @components while ($group_name eq "");
+
+ # check that the group doesn't exist already
+ if (!defined $group->{groups}{$group_name}) {
+ return &report_error (305, gettext("Group $operand does not exists"));
+ };
+
+ # remove directory from the SUBDIRS macro in the parent group
+ ¯o_remove_text ($makefile, "SUBDIRS", $group_name, 1);
+
+ # add Makefile to list of configure generated files
+ my $local_new_dir = $group->{prefix} . $group_name;
+ $local_new_dir =~ s/^\///;
+ edit_config_files $project->{configure_in}, "remove", "$local_new_dir/Makefile";
+
+ ## Remove group from internal hashes
+ delete $project->{all_groups}->{$operand};
+ delete $group->{groups}->{$group_name};
+ }
+ elsif ($op eq "set_config") {
+ KEY:
+ foreach my $key (keys %$operand) {
+ my $value = $operand->{$key};
+
+ if ($key eq "includes") {
+ ¯o_rewrite ($makefile, "INCLUDES", $value);
+ } elsif ($key eq "amcflags") {
+ ¯o_rewrite ($makefile, "AM_CFLAGS", $value);
+ } elsif ($key eq "amcppflags") {
+ ¯o_rewrite ($makefile, "AM_CPPFLAGS", $value);
+ } elsif ($key eq "amcxxflags") {
+ ¯o_rewrite ($makefile, "AM_CXXFLAGS", $value);
+ } elsif ($key eq "amgcjflags") {
+ ¯o_rewrite ($makefile, "AM_GCJFLAGS", $value);
+ } elsif ($key eq "amjavaflags") {
+ ¯o_rewrite ($makefile, "AM_JAVAFLAGS", $value);
+ } elsif ($key eq "amfflags") {
+ ¯o_rewrite ($makefile, "AM_FFLAGS", $value);
+ } elsif ($key eq "installdirs") {
+ foreach my $item (keys %$value) {
+ if ($value->{$item} =~ /^\s*$/) {
+ ¯o_remove ($makefile, $item."dir");
+ } else {
+ ¯o_rewrite ($makefile, $item."dir", $value->{$item});
+ }
+ }
+ }
+ }
+ }
+ else {
+
+ return &report_error (300, gettext("Invalid operation '$op' to group_op_handler"));
+ }
+ # success!
+ return 0;
+}
+
+sub project_op_handler
+{
+ my ($project, $op, $operand) = @_;
+ my $configure_in = $project->{"configure_in"};
+
+ if ($verbose) {
+ print "Operation = $op\n";
+ print "operand start\n";
+ print Dumper $operand;
+ print "operand end\n";
+ }
+
+ # op is: "set_config"
+ # operand is full group name for add_group, and ignored for remove_group
+ my $package_name = $project->{config}->{package_name};
+ my $package_version = $project->{config}->{package_version};
+ my $package_url = $project->{config}->{package_url};
+
+ if ($op eq "set_config") {
+ KEY:
+ foreach my $key (keys %$operand) {
+ my $value = $operand->{$key};
+
+ if ($key eq "variables") {
+ foreach my $var (keys %$value) {
+ if ($value->{$var} =~ /^\s*$/) {
+ &configure_remove_variable ($configure_in, $var);
+ } else {
+ &configure_rewrite_variable ($configure_in, $var,
+ $value->{$var});
+ }
+ }
+ } elsif ($key =~ /^pkg_check_modules_(.*)$/) {
+ my $module = $1;
+ foreach my $subkey (keys %$value) {
+ if ($subkey eq "packages") {
+ my $packages = $value->{$subkey};
+ &configure_rewrite_packages ($configure_in, $module,
+ $packages);
+ }
+ }
+ } elsif ($key eq "package_name") {
+ $package_name = $value;
+ configure_rewrite_package_info ($configure_in, $package_name,
+ $package_version, $package_url);
+ } elsif ($key eq "package_version") {
+ $package_version = $value;
+ configure_rewrite_package_info ($configure_in, $package_name,
+ $package_version, $package_url);
+ } elsif ($key eq "package_url") {
+ $package_url = $value;
+ configure_rewrite_package_info ($configure_in, $package_name,
+ $package_version, $package_url);
+ }
+ }
+ }
+ else {
+
+ return &report_error (300, gettext("Invalid operation '$op' to group_op_handler"));
+ }
+ # success!
+ return 0;
+}
+
+sub unimplemented_writer
+{
+ my ($project, $group, $op, $target, $operand) = @_;
+
+ # group is the group hash in which to do the operation
+ # op is: "add_target", "remove_target", "set_config", "add_source", "remove_source"
+ # target is:
+ # - the new target name when add_target
+ # - a ref to the target hash to operate on otherwise
+ # operand is:
+ # - ignored for remove_target
+ # - target type for add_target
+ # - the source uri to add/remove on {add,remove}_source
+ # - a config hash on set_config containing any/all fields in group config
+ # FIXME: handle dependencies
+
+ return &report_error (302, gettext("Unimplemented target type writer"));
+}
+
+sub compiled_primary_target_writer
+{
+ my ($project, $group, $op, $target, $operand) = @_;
+ my $makefile = $group->{makefile};
+
+ my %primaries = ( program => "PROGRAMS",
+ static_lib => "LIBRARIES",
+ shared_lib => "LTLIBRARIES" );
+
+ &debug ("compiled primary writer: $op on group $group with $target($operand)");
+
+ # FIXME: Modify the $group variable as well as the makefile (worth it?...
+ # only if we have to do a sequence of operations on the same target)
+
+ if ($op eq "add_target") {
+ # $target contains the target name, which in this case is the name of
+ # the compiled objects (program or library)
+ # $operand contains the target type
+ my $canonical = &canonicalize_name ($target);
+ my ($primary, $prefix);
+
+ my %default_prefixes = ( program => "bin",
+ static_lib => "lib",
+ shared_lib => "lib" );
+
+ $primary = $primaries{$operand};
+ $prefix = $default_prefixes{$operand};
+
+ if ($target eq "") {
+ return &report_error (301, gettext("Invalid empty target name"));
+ }
+
+ if (!defined $primary) {
+ return &report_error (301, gettext("Invalid target type '$operand' to compiled_primary_target_writer"));
+ }
+
+ # FIXME: verify that the target doesn't yet exist
+ ¯o_append_text ($makefile, "${prefix}_${primary}", $target);
+ ¯o_create ($makefile, "${canonical}_SOURCES", "", "${prefix}_${primary}");
+
+ }
+ elsif ($op eq "remove_target") {
+ my $canonical = &canonicalize_name ($target->{name});
+ my $prefix = $target->{config}{installdir};
+ my $primary = $primaries{$target->{type}};
+
+ # FIXME: what if the target is in a macro which is used in prefix_PRIMARY
+ ¯o_remove_text ($makefile, "${prefix}_${primary}", $target->{name}, 1);
+ ¯o_remove_text ($makefile, "EXTRA_${primary}", $target->{name}, 1);
+ ¯o_remove ($makefile,
+ "${canonical}_SOURCES",
+ "EXTRA_${canonical}_SOURCES",
+ "${canonical}_LDADD",
+ "${canonical}_LIBADD",
+ "${canonical}_LDFLAGS",
+ "${canonical}_DEPENDENCIES",
+ "${canonical}_CFLAGS",
+ "${canonical}_CPPFLAGS",
+ "${canonical}_CXXFLAGS",
+ "${canonical}_GCJFLAGS",
+ "${canonical}_FFLAGS",
+ );
+ ## FIXME: The line below reports error.
+ ## &rule_remove ($makefile, "\$(${canonical}_OBJECTS)");
+
+ ## Remove the target from internal hash
+ my $target_id = $target->{id};
+ delete $group->{targets}->{$target_id};
+ }
+ elsif ($op eq "set_config") {
+ my $canonical = &canonicalize_name ($target->{name});
+ my $primary = $primaries{$target->{type}};
+
+ KEY:
+ foreach my $key (keys %$operand) {
+ my $value = $operand->{$key};
+
+ if ($key eq "installdir") {
+ my $old_prefix = $target->{config}{installdir};
+ my $new_prefix = &check_primary_prefix ($value, $primary, $group);
+ if (!$new_prefix) {
+ next KEY;
+ }
+
+ # FIXME: handle the EXTRA prefix
+ &debug ("Removing $target->{name} from ${old_prefix}_${primary}");
+ ¯o_remove_text ($makefile, "${old_prefix}_${primary}",
+ $target->{name}, 1);
+ ¯o_append_text ($makefile, "${new_prefix}_${primary}",
+ $target->{name});
+ } elsif ($key eq "ldflags") {
+ if ($value !~ /^\s*$/) {
+ ¯o_rewrite ($makefile, "${canonical}_LDFLAGS",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_LDFLAGS");
+ };
+ } elsif ($key eq "cflags") {
+ if ($value !~ /^\s*$/) {
+ ¯o_rewrite ($makefile, "${canonical}_CFLAGS",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_CFLAGS");
+ };
+ } elsif ($key eq "cppflags") {
+ if ($value !~ /^\s*$/) {
+ ¯o_rewrite ($makefile, "${canonical}_CPPFLAGS",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_CPPFLAGS");
+ };
+ } elsif ($key eq "cxxflags") {
+ if ($value !~ /^\s*$/) {
+ ¯o_rewrite ($makefile, "${canonical}_CXXFLAGS",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_CXXFLAGS");
+ };
+ } elsif ($key eq "gcjflags") {
+ if ($value !~ /^\s*$/) {
+ ¯o_rewrite ($makefile, "${canonical}_GCJFLAGS",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_GCJFLAGS");
+ };
+ } elsif ($key eq "fflags") {
+ if ($value !~ /^\s*$/) {
+ ¯o_rewrite ($makefile, "${canonical}_FFLAGS",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_FFLAGS");
+ };
+ } elsif ($key eq "ldadd") {
+ if ($value !~ /^\s*$/) {
+ ¯o_rewrite ($makefile, "${canonical}_LDADD",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_LDADD");
+ };
+ } elsif ($key eq "libadd") {
+ if ($value !~ /^\s*$/) {
+ ¯o_rewrite ($makefile, "${canonical}_LIBADD",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_LIBADD");
+ };
+ } elsif ($key eq "explicit_deps") {
+ # FIXME: this should perhaps be handled via sources add/remove
+ if ($value !~ /^\s*$/) {
+ ¯o_rewrite ($makefile, "${canonical}_DEPENDENCIES",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_DEPENDENCIES");
+ };
+ }
+ }
+ }
+ elsif ($op eq "add_source") {
+ my $canonical = &canonicalize_name ($target->{name});
+ my $var = "${canonical}_SOURCES";
+ my $rel_source;
+
+ # Note: we expect the operand to be the absolute to the root of the project
+ # i.e. for group /src/, the operand will be /src/file.c
+ $rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+ &debug ("Will modify $var using source $rel_source");
+ my %macros = %{$makefile->{macros}};
+ if (!exists ($macros{$var})) {
+ # Need to add the default source the the new var
+ $operand = "$target->{name}.c " . $rel_source;
+ }
+ ¯o_append_text ($makefile, $var, $rel_source);
+
+ }
+ elsif ($op eq "remove_source") {
+ my $canonical = &canonicalize_name ($target->{name});
+ my $var = "${canonical}_SOURCES";
+ my $rel_source;
+
+ # Note: we expect the operand to be the absolute to the root of the project
+ # i.e. for group /src/, the operand will be /src/file.c
+
+ # FIXME: this needs lot of work, since only works for literal filnanmes
+ # we need to identify where is this filename in expanded from a macro or
+ # if it has some variable like $(srcdir)
+ $rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+ # FIXME: check for duplicate sources
+
+ &debug ("Will modify $var using source $rel_source");
+ if (!exists ($makefile->{macros}{$var}) &&
+ $rel_source eq "${$target->{name}}.c") {
+ # Create the empty macro if the removed source is the default one
+ ¯o_create ($makefile, $var, "");
+ } else {
+ ¯o_remove_text ($makefile, $var, $rel_source);
+ }
+
+ }
+ else {
+ return &report_error (300, gettext("Invalid operation '$op' to program_target_writer"));
+ };
+
+ # Success!
+ return 0;
+}
+
+sub hidden_in_another_var {
+ my ($makefile, $macro_name, $file) = @_;
+
+ my @parts = split (/\s+/, $makefile->{macros}->{$macro_name}->{contents});
+
+ foreach my $part (@parts) {
+ # FIXME: Are there more characters allowed in macro names than a-zA-Z0-9_?
+ if ($part =~ m/^\$\(([a-zA-Z0-9_]*)\)$/) {
+ my $var = $1;
+ my $content = $makefile->{macros}->{$var}->{contents};
+ my @sub_parts = split (/\s+/, $content);
+ foreach my $sub_part (@sub_parts) {
+ if ($sub_part eq $file) {
+ return $var;
+ }
+ }
+ }
+ }
+
+ return undef;
+}
+
+sub simple_extra_dist_target_writer
+{
+ my ($project, $group, $op, $target, $operand) = @_;
+ my $makefile = $group->{makefile};
+
+ &debug ("simple extra target writer: $op on group $group with $target($operand)");
+
+ if ($op eq "add_target") {
+ if (exists($makefile->{macros}->{EXTRA_DIST}) and $makefile->{macros}->{EXTRA_DIST}->{content} !~ m/^\s*$/) {
+ return &report_error (305, gettext("An extra target already exists for ") . $group->{prefix})
+ }
+ }
+ elsif ($op eq "remove_target") {
+ ¯o_remove ($makefile, "EXTRA_DIST");
+ }
+ elsif ($op eq "add_source") {
+ my $rel_source;
+
+ # Note: we expect the operand to be the absolute to the root of the project
+ # i.e. for group /src/, the operand will be /src/file.c
+ $rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+ &debug ("adding $rel_source to EXTRA_DIST");
+ # Note: we expect the uri to be relative to the group
+ ¯o_append_text ($makefile, "EXTRA_DIST", $rel_source);
+ }
+ elsif ($op eq "remove_source") {
+ my $rel_source;
+
+ # Note: we expect the operand to be the absolute to the root of the project
+ # i.e. for group /src/, the operand will be /src/file.c
+ $rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+ if (defined(my $original_pkg = hidden_in_another_var($makefile, "EXTRA_DIST", $rel_source))) {
+ return &report_error (305, gettext("Could not remove file because it is part of group '$original_pkg'. Please delete it there."));
+ }
+
+ &debug ("removing $rel_source from EXTRA_DIST");
+ # Note: we expect the uri to be relative to the group
+ ¯o_remove_text ($makefile, "EXTRA_DIST", $rel_source);
+ }
+ else {
+ return &report_error (300, gettext("Invalid operation '$op' to program_target_writer"));
+ };
+
+ # Success!
+ return 0;
+}
+
+sub simple_primary_target_writer
+{
+ my ($project, $group, $op, $target, $operand) = @_;
+ my $makefile = $group->{makefile};
+
+ my %primaries = ( data => "DATA",
+ script => "SCRIPTS",
+ headers => "HEADERS",
+ java => "JAVA",
+ python => "PYTHON");
+
+ &debug ("simple primary writer: $op on group $group with $target($operand)");
+
+ # FIXME: Modify the $group variable as well as the makefile (worth it?...
+ # only if we have to do a sequence of operations on the same target)
+
+ if ($op eq "add_target") {
+ # WARNING, Convention: $target for simple primaries is actually $prefix:$type
+ # $operand also contains the target type
+ my ($primary, $prefix, $check_primary);
+
+ ($prefix, $check_primary) = split /:/, $target;
+
+ $primary = $primaries{$operand};
+ if (!defined $primary) {
+ return &report_error (301, gettext("Invalid target type '$operand' to simple_primary_target_writer"));
+ }
+
+ if ($check_primary ne $operand) {
+ &report_warning (302, gettext("The target type supplied in the target name $target and the given target type '$operand' don't match. Will use the one provided in the name"));
+ }
+
+ if (!&check_primary_prefix ($prefix, $primary, $group)) {
+ return;
+ }
+
+ ¯o_create ($makefile, "${prefix}_${primary}", "");
+
+ ## Add installation dir
+ if ($primary eq "DATA") {
+ ¯o_create ($makefile, "${prefix}dir", '$(pkgdatadir)', "${prefix}_${primary}");
+ }
+ elsif ($primary eq "HEADERS") {
+ ¯o_create ($makefile, "${prefix}dir", '$(pkgincludedir)', "${prefix}_${primary}");
+ }
+
+ ## For data primary target DATA and HEADERS, also add it to EXTRA_DIST
+ if ($primary eq "DATA" || $primary eq "HEADERS")
+ {
+ ¯o_append_text ($makefile, "EXTRA_DIST",
+ "\$\(${prefix}_${primary}\)");
+ }
+ }
+ elsif ($op eq "remove_target") {
+ my $prefix = $target->{config}{installdir};
+ my $primary = $primaries{$target->{type}};
+
+ ¯o_remove ($makefile, "${prefix}_${primary}");
+
+ ## Remove from EXTRA_DIST too.
+ ¯o_remove_text ($makefile, "EXTRA_DIST",
+ "\$\(${prefix}_${primary}\)");
+
+ ¯o_remove ($makefile, "${prefix}dir");
+
+ }
+ elsif ($op eq "set_config") {
+ # Nothing to do here, since these kind of targets are not relocatable
+ }
+ elsif ($op eq "add_source") {
+ my $prefix = $target->{config}{installdir};
+ my $primary = $primaries{$target->{type}};
+ my $var = "${prefix}_${primary}";
+ my $rel_source;
+
+ # Note: we expect the operand to be the absolute to the root of the project
+ # i.e. for group /src/, the operand will be /src/file.c
+ $rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+ # Note: we expect the uri to be relative to the group
+ ¯o_append_text ($makefile, $var, $rel_source);
+ }
+ elsif ($op eq "remove_source") {
+ my $prefix = $target->{config}{installdir};
+ my $primary = $primaries{$target->{type}};
+ my $var = "${prefix}_${primary}";
+ my $rel_source;
+
+ # Note: we expect the operand to be the absolute to the root of the project
+ # i.e. for group /src/, the operand will be /src/file.c
+ $rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+ &debug ("removing $rel_source from $var macro");
+ # Note: we expect the uri to be relative to the group
+ ¯o_remove_text ($makefile, $var, $rel_source);
+ }
+ else {
+ return &report_error (300, gettext("Invalid operation '$op' to program_target_writer"));
+ };
+
+ # Success!
+ return 0;
+}
+
+
+######################################################################
+#### 7. XML PRINTING FUNCTIONS #####################################
+######################################################################
+
+my ($indent_level, $have_vspace);
+
+$indent_level = 0;
+$have_vspace = 0;
+
+sub xml_enter { $indent_level += 2; }
+sub xml_leave { $indent_level -= 2; }
+sub xml_indent { print " " x $indent_level; $have_vspace = 0; }
+sub xml_vspace { if (not $have_vspace) { print "\n"; $have_vspace = 1; } }
+sub xml_print { &xml_indent; print @_; }
+
+sub xml_escape
+{
+ $_ = $_[0];
+ s/\&/\&/g;
+ s/\</\</g;
+ s/\>/\>/g;
+ s/\"/\"/g;
+ s/\'/\'/g;
+ ## s/\\/\\\\/g;
+ ## s/\n/\\n/g;
+ ## s/\t/\\t/g;
+ return $_;
+}
+
+sub xml_unescape
+{
+ $_ = $_[0];
+ s/\&/\&/g;
+ s/\</\</g;
+ s/\>/\>/g;
+ s/\"/\"/g;
+ s/\'/\'/g;
+ ## s/\\\\/\\/g;
+ ## s/\\n/\n/g;
+ ## s/\\t/\t/g;
+ return $_;
+}
+
+sub xml_print_source
+{
+ my ($source, $prefix) = @_;
+
+ &xml_enter ();
+ &xml_print ("<source uri='" . xml_escape(reduce_path ($prefix . $source)) . "'/>\n");
+ &xml_leave ();
+}
+
+sub xml_print_dep
+{
+ my ($dep, $prefix) = @_;
+
+ my ($target, $file) = split ';', $dep;
+ &xml_enter ();
+ &xml_print ("<dependency file='" .
+ xml_escape(reduce_path ($prefix . $file)) .
+ "' target='" . xml_escape($target) . "'/>\n");
+ &xml_leave ();
+}
+
+sub xml_print_target
+{
+ my ($target, $group) = @_;
+ my $sr;
+
+ &xml_enter ();
+
+ &xml_print ("<target name='" . xml_escape($target->{name} ) . "' type='" . xml_escape($target->{type}) .
+ "' id='" . xml_escape($target->{id}) . "'>\n");
+ # Print the target config
+ &xml_print_config ($target->{config}, ());
+
+ foreach $sr (@{$target->{sources}}) {
+ &xml_print_source ($sr, $group->{prefix});
+ }
+ foreach $sr (@{$target->{dependencies}}) {
+ &xml_print_dep ($sr, $group->{prefix});
+ };
+ &xml_print ("</target>\n");
+
+ &xml_leave ();
+}
+
+###
+# group_print_xml (group [, which])
+# recursively prints the xml representation of the group
+# if which is "changed" only prints the group if it's flaged as changed
+sub group_print_xml
+{
+ my $group = shift;
+ my $which = shift || "all";
+
+ if ($which ne "changed" || $group->{changed}) {
+ &xml_enter ();
+ &xml_print ("<group name='" . xml_escape($group->{name}) . "' id='" . xml_escape($group->{prefix}) . "' ".
+ "source='" . xml_escape($group->{makefile}{filename}) . "'>\n");
+ }
+
+ # Print each subgroup
+ foreach my $gr (values %{$group->{groups}}) {
+ &group_print_xml ($gr, $which);
+ }
+
+ if ($which ne "changed" || $group->{changed}) {
+ # Print the group config
+ &xml_print_config ($group->{config}, ());
+
+ # Print the targets
+ foreach my $tr (values %{$group->{targets}}) {
+ &xml_print_target ($tr, $group);
+ }
+ &xml_print ("</group>\n");
+
+ &xml_leave ();
+ }
+}
+
+sub xml_print_config
+{
+ my ($config, @except) = @_;
+ my $value;
+
+ &xml_enter ();
+ &xml_print ("<config>\n");
+ foreach my $key (keys %{$config}) {
+ if (($key =~ /_order$/) || (grep $_ eq $key, @except)) {
+ next;
+ };
+
+ &xml_enter ();
+ $value = &empty_if_undef ($config->{$key});
+ if (ref ($value) eq "ARRAY" && @$value) {
+ # It's a list
+ &xml_print ("<param name=\"$key\">\n");
+ &xml_enter ();
+ foreach (@$value) {
+ ## Remove white spaces from config parameters.
+ $_ =~ s/\s/ /gs;
+ $_ =~ s/\s+/ /gs;
+ $_ = &xml_escape ($_);
+ &xml_print ("<item value=\"$_\"/>\n");
+ };
+ &xml_leave ();
+ &xml_print ("</param>\n");
+ } elsif (ref ($value) eq "HASH" && %$value) {
+ # It's a hash
+ my @value_order;
+ if (defined ($config->{"${key}_order"}) &&
+ ref ($config->{"${key}_order"}) eq 'ARRAY') {
+ @value_order = @{$config->{"${key}_order"}};
+ }
+ if (@value_order <= 0) {
+ @value_order = keys %$value;
+ }
+ &xml_print ("<param name=\"$key\">\n");
+ &xml_enter ();
+ foreach my $item (@value_order) {
+ ## Remove white spaces from config parameters.
+ my $val = $value->{$item};
+ $val =~ s/\s/ /gs;
+ $val =~ s/\s+/ /gs;
+ $_ = &xml_escape ($val);
+ &xml_print ("<item name=\"$item\" value=\"$_\"/>\n");
+ };
+ &xml_leave ();
+ &xml_print ("</param>\n");
+ } elsif (! ref ($value) && $value) {
+ ## Remove white spaces from config parameters.
+ $value =~ s/\s/ /gs;
+ $value =~ s/\s+/ /gs;
+ $value = &xml_escape ($value);
+ &xml_print ("<param name=\"$key\" value=\"$value\"/>\n");
+ };
+ &xml_leave ();
+ };
+ &xml_print ("</config>\n");
+ &xml_leave ();
+}
+
+###
+# project_print_xml (project [, which])
+# print xml representation of the project
+# if which is "changed" only prints changed groups in last modification
+sub project_print_xml
+{
+ my $project = shift;
+ my $which = shift || "all";
+
+ print "<?xml version='1.0' encoding='ISO-8859-1' standalone='yes'?>\n";
+ print "<!DOCTYPE project []>\n\n";
+ print "<project root=\"$project->{prefix}\" report=\"" .
+ ($which eq "changed" ? "partial" : "full") .
+ "\" source=\"$project->{configure_in}{filename}\">\n";
+
+ # Print project config.
+ &xml_print_config ($project->{config}, ());
+
+ # Print the groups
+ &group_print_xml ($project->{root_group}, $which);
+
+ print "</project>\n";
+}
+
+sub recursive_print
+{
+ my ($level, $el) = @_;
+ my $pad = " " x (2 * $level);
+ if (!ref ($el)) {
+ print $pad, $el, "\n";
+ } elsif (ref ($el) eq "ARRAY") {
+ foreach my $sel (@$el) {
+ &recursive_print ($level + 1, $sel);
+ }
+ } elsif (ref ($el) eq "HASH") {
+ foreach my $sel (keys %$el) {
+ print $pad, $sel, " => ", $el->{$sel}, "\n";
+ }
+ }
+}
+
+
+######################################################################
+#### 8. XML SCANNING ###############################################
+######################################################################
+
+sub xml_scan_make_kid_array
+{
+ my %hash = ();
+ my (@sublist, @attr);
+
+ @attr = $_[0] =~ /[^\s]+\s*([a-zA-Z_-]+)\s*\=\s*\"([^\"]*)/g;
+ %hash = @attr;
+
+ push @sublist, \%hash;
+ return \ sublist;
+}
+
+sub xml_scan_recurse
+{
+ my ($tree, $scan_ref) = @_;
+
+ my @list = @$tree;
+ my ($el, $sublist);
+
+ while (@$scan_ref) {
+ $el = shift @$scan_ref;
+
+ # Empty strings, PI and DTD must go.
+ if (($el eq "") || $el =~ /^\<[!?].*\>$/s) { next; }
+
+ if ($el =~ /^\<.*\/\>$/s) {
+ # Empty.
+ $el =~ /^\<([a-zA-Z_-]+).*\/\>$/s;
+ push @list, $1;
+ push @list, &xml_scan_make_kid_array ($el);
+
+ } elsif ($el =~ /^\<\/.*\>$/s) {
+ # End.
+ last;
+
+ } elsif ($el =~ /^\<.*\>$/s) {
+ # Start.
+ $el =~ /^\<([a-zA-Z_-]+).*\>$/s;
+ push @list, $1;
+ $sublist = &xml_scan_make_kid_array ($el);
+ push @list, &xml_scan_recurse ($sublist, $scan_ref);
+ next;
+
+ } elsif ($el ne "") {
+ # PCDATA.
+ push @list, 0;
+ push @list, "$el";
+ }
+ }
+
+ return \ list;
+}
+
+sub xml_scan
+{
+ my $input_file = $_[0];
+ my ($doc, $tree, $i);
+
+ if (!$input_file || $input_file eq "-") {
+ $doc .= $i while ($i = <STDIN>);
+
+ } else {
+ if (open INPUT_FILE, $input_file) {
+ $doc .= $i while ($i = <INPUT_FILE>);
+ close INPUT_FILE;
+ }
+ else {
+ &report_error (4, gettext("Can't open input file '$input_file'"));
+ return [];
+ }
+ }
+
+ &debug ("Got input: $doc");
+
+ my @xml_scan_list = ($doc =~ /([^\<]*)(\<[^\>]*\>)[ \t\n\r]*/mg); # pcdata, tag, pcdata, tag, ...
+
+ $tree = &xml_scan_recurse ([], \ xml_scan_list);
+
+ return $tree;
+}
+
+
+######################################################################
+#### 9. XML PROCESSING #############################################
+######################################################################
+
+sub xml_parse_params
+{
+ my $tree = $_[0];
+ my %params;
+
+ shift @$tree;
+
+ while (@$tree && $$tree[0] eq "param") {
+ shift @$tree;
+ my $child = $$tree[0];
+ if (defined($$child[0]->{value})) {
+ my $str = $$child[0]->{value};
+ $params{$$child[0]->{name}} = xml_unescape ($str);
+ } else {
+ my %items;
+ $params{$$child[0]->{name}} = \%items;
+ shift @$child;
+ while (@$child && $$child[0] eq "item") {
+ shift @$child;
+ my $str = $$child[0][0]->{value};
+ $items{$$child[0][0]->{name}} = xml_unescape ($str);
+ shift @$child;
+ }
+ }
+ shift @$tree;
+ }
+ return \%params;
+}
+
+sub xml_parse_add
+{
+ my $tree = $_[0];
+ my $type;
+ my ($parent, $curr);
+
+ my %new_op = ( op => "",
+ group_name => "",
+ target_name => "",
+ operand => "" );
+
+ $type = $$tree[0]->{type};
+ $new_op{op} = "add_$type";
+
+ shift @$tree;
+
+ $parent = $$tree[0];
+ $curr = $$tree[1];
+
+ while (1) {
+ if ($parent eq "group") {
+ # The group id is unique
+ $new_op{group_name} = $$curr[0]->{id};
+ } elsif ($parent eq "target") {
+ $new_op{target_name} = $$curr[0]->{id};
+ if ($type eq "target") {
+ $new_op{operand} = $$curr[0]->{type};
+ }
+ } elsif ($parent eq "source") {
+ $new_op{operand} = $$curr[0]->{uri};
+ }
+
+ if ($parent eq $type) {
+ last;
+ }
+
+ shift @$curr;
+ $parent = $$curr[0];
+ $curr = $$curr[1];
+ }
+
+ return \%new_op;
+}
+
+sub xml_parse_change
+{
+}
+
+sub xml_parse_set
+{
+ my $tree = $_[0];
+ my $type;
+ my ($parent, $curr);
+
+ my %new_op = ( op => "",
+ group_name => "",
+ target_name => "",
+ operand => "" );
+ $new_op{operand} = {};
+
+ $type = $$tree[0]->{type};
+ $new_op{op} = "set_$type";
+
+ shift @$tree;
+
+ $parent = $$tree[0];
+ $curr = $$tree[1];
+
+ while (1) {
+ if ($parent eq "group") {
+ $new_op{group_name} = $$curr[0]->{id};
+ } elsif ($parent eq "target") {
+ $new_op{target_name} = $$curr[0]->{id};
+ } elsif ($parent eq "config") {
+ $new_op{operand} = xml_parse_params($curr);
+ }
+
+ if ($parent eq $type) {
+ last;
+ }
+
+ shift @$curr;
+ $parent = $$curr[0];
+ $curr = $$curr[1];
+ }
+
+ return \%new_op;
+}
+
+sub xml_parse_remove
+{
+ my $tree = $_[0];
+ my $type;
+ my ($parent, $curr);
+
+ my %new_op = ( op => "",
+ group_name => "",
+ target_name => "",
+ operand => "" );
+
+ $type = $$tree[0]->{type};
+ $new_op{op} = "remove_$type";
+
+ shift @$tree;
+
+ $parent = $$tree[0];
+ $curr = $$tree[1];
+
+ while (1) {
+ if ($parent eq "group") {
+ # The group name is unique
+ $new_op{group_name} = $$curr[0]->{id};
+ } elsif ($parent eq "target") {
+ $new_op{target_name} = $$curr[0]->{id};
+ } elsif ($parent eq "source") {
+ $new_op{operand} = $$curr[0]->{uri};
+ }
+
+ if ($parent eq $type) {
+ last;
+ }
+
+ shift @$curr;
+ $parent = $$curr[0];
+ $curr = $$curr[1];
+ }
+ return \%new_op;
+}
+
+sub xml_parse_ops
+{
+ my $tree = $_[0];
+ my @ops;
+
+ shift @$tree; # Skip attributes.
+
+ while (@$tree) {
+ if ($$tree[0] eq "add") {
+ push @ops, &xml_parse_add ($$tree[1]);
+ }
+ elsif ($$tree[0] eq "change") {
+ push @ops, &xml_parse_change ($$tree[1]);
+ }
+ elsif ($$tree[0] eq "set") {
+ push @ops, &xml_parse_set ($$tree[1]);
+ }
+ elsif ($$tree[0] eq "remove") {
+ push @ops, &xml_parse_remove ($$tree[1]);
+ }
+
+ shift @$tree;
+ shift @$tree;
+ }
+
+ return \ ops;
+}
+
+
+######################################################################
+#### 10. HELPER FUNCTIONS ##########################################
+######################################################################
+
+sub path_relative_to_absolute
+{
+ my ($prefix, $rel) = @_;
+
+ if (substr ($prefix, -1) ne "/") {
+ $prefix .= "/";
+ };
+
+ return reduce_path ($prefix . $rel);
+}
+
+sub path_absolute_to_relative
+{
+ my ($prefix, $absolute) = @_;
+
+ my @prefix_parts = split '/', $prefix;
+ my @absolute_parts = split '/', $absolute;
+ my @result = ();
+ my $append_parent = 0;
+
+ # If prefix is root, assume single empty field so that leading '/'
+ # is stripped from the given abosulte path to form the relative path.
+ @prefix_parts = ("") if ($prefix eq "/");
+
+ foreach my $part (@prefix_parts) {
+ my $abs_part = shift @absolute_parts;
+
+ if ($part eq $abs_part && !$append_parent) {
+ next;
+ } else {
+ $append_parent = 1;
+ unshift @result, "..";
+ push @result, $abs_part;
+ };
+
+ };
+ push @result, @absolute_parts;
+
+ return join ('/', @result);
+}
+
+sub reduce_path
+{
+ my ($uri) = @_;
+
+ my @result = ();
+
+ foreach my $part (split '/', $uri) {
+ if ($part eq "..") {
+ # Preserve a leading /../
+ if (@result > 0 && $result[$#result] ne "") {
+ pop @result;
+ } else {
+ push @result, $part;
+ }
+ } elsif ($part eq ".") {
+ next;
+ } else {
+ push @result, $part;
+ };
+ };
+
+ return (join '/', @result);
+}
+
+sub remove_files_from_sources
+{
+ my ($var, $group) = @_;
+
+ my %targets = %{$group->{targets}};
+
+ # Strips from $var all those files already present in some target's sources
+ $var = " $var ";
+ foreach my $target (values %targets) {
+ foreach my $source (@{$target->{sources}}) {
+ $var =~ s/\s\Q$source\s/ /g;
+ };
+ };
+ return &trim ($var);
+}
+
+sub remove_files_from_built_files
+{
+ my ($var, $group) = @_;
+
+ my %targets = %{$group->{targets}};
+
+ # Strips from $var all those files already present in some target's built_file,
+ # returning the target name
+ my @targets_found;
+ $var = " $var ";
+ foreach my $target (keys %targets) {
+ foreach my $built_file (@{$targets{$target}{built_files}}) {
+ if ($var =~ /\s\Q$built_file\E\s/) {
+ $var =~ s/\s\Q$built_file\E\s/ /g;
+ push @targets_found, "$target;$built_file";
+ };
+ };
+ };
+ # Now remove those files present in BUILT_SOURCES
+ foreach my $built_file (split /\s+/, $group->{config}{built_sources}) {
+ $var =~ s/\s$built_file\s/ /g;
+ }
+
+ return (&trim ($var), @targets_found);
+}
+
+sub get_sources_by_extension
+{
+ my ($group, $ext) = @_;
+
+ my @result;
+
+ foreach my $target (values %{$group->{targets}}) {
+ my @sources = @{$target->{sources}};
+ push @result, grep /$ext\z/, @sources;
+ };
+ return @result;
+}
+
+sub make_absolute_path
+{
+ my $path = $_[0];
+
+ if (substr ($path, 0, 1) ne "/") {
+ # the path is not absolute
+ my $cwd = `pwd`;
+ chomp $cwd;
+ $path = "$cwd/$path";
+ };
+
+ return reduce_path ($path);
+}
+
+######################################################################
+#### 11. MAIN PROGRAM ##############################################
+######################################################################
+
+my ($op, $arg, $newop);
+
+$op = "";
+while (@ARGV) {
+ $_ = shift @ARGV;
+ if ($_ eq "--get" || $_ eq "-g") { $newop = "get"; }
+ elsif ($_ eq "--set" || $_ eq "-s") { $newop = "set"; }
+ elsif ($_ eq "--verbose" || $_ eq "-v") { $verbose = 1; }
+ elsif ($_ eq "--dry-run" || $_ eq "-n") { $dry_run = 1; }
+ elsif ($_ eq "--debug" || $_ eq "-d") { &enable_debug(); }
+
+ elsif ($_ eq "--test-scan") { $newop = "test-scan"; }
+ else {
+ if ($arg) {
+ &report_error (5, gettext("You can't specify more than one project dir/file"));
+ exit 5;
+ }
+ $arg = $_;
+ }
+ if ($newop) {
+ if ($op) {
+ &report_error (5, gettext("You can't specify more than one operation"));
+ exit 5;
+ }
+ $op = $newop;
+ $newop = "";
+ }
+}
+
+if ($op eq 'get') {
+ #########################################
+ # Get the project XML view
+ #########################################
+
+ my $project_dir = make_absolute_path ($arg);
+
+ if (!$project_dir || ! -d $project_dir || ! -e $project_dir) {
+ &report_error (2, gettext("Project root directory doesn't exist"));
+ exit 2;
+ };
+
+ my $project = &process_project ($project_dir);
+
+ if ($project) {
+ &project_print_xml ($project);
+ };
+
+}
+elsif ($op eq "test-scan") {
+ #########################################
+ # Test XML scanning
+ #########################################
+
+ my $tree = &xml_scan ($arg);
+ &recursive_print (-1, $tree);
+
+}
+elsif ($op eq "set") {
+ ########################################
+ # Project modification
+ ########################################
+
+ my $tree = &xml_scan ($arg);
+
+ # Walk the tree recursively and extract configuration parameters.
+
+ while (@$tree) {
+ if ($$tree[0] eq "project") {
+ # Get the project and execute
+ my $project_dir = make_absolute_path ($$tree [1][0]->{root});
+ my $project = &process_project ($project_dir);
+ my $ops = &xml_parse_ops ($$tree[1]);
+ if ($project && $ops) {
+ # reset groups changed flag so we later know what
+ # groups changed and need to be fed back to the user
+ &project_reset_changed ($project);
+
+ # perform the operations
+ if (&project_operate ($project, $ops)) {
+ # print changed groups
+ &project_print_xml ($project, "changed");
+ }
+ };
+ }
+ shift @$tree;
+ shift @$tree;
+ }
+}
+else {
+ &report_warning (0, gettext("Nothing to do"));
+}
Added: trunk/plugins/gbf-am/gbf-am-plugin-48.png
==============================================================================
Binary file. No diff available.
Added: trunk/plugins/gbf-am/gbf-am-project.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-project.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,3796 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-am-project.c
+ *
+ * Copyright (C) 2000 JP Rosevear
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: JP Rosevear
+ * Author: Naba Kumar
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <glib/gi18n.h>
+#include <libgnome/gnome-macros.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libgnomevfs/gnome-vfs-monitor.h>
+#include <libgnomevfs/gnome-vfs-ops.h>
+#include <libgnomevfs/gnome-vfs-utils.h>
+#include <libgnomevfs/gnome-vfs-xfer.h>
+#include "gbf-am-project.h"
+#include "gbf-am-config.h"
+#include "gbf-am-properties.h"
+
+/* With debugging enable, the perl script gbf-am-parse outputs
+ * debugging messages not in xml format and the parser
+ * return an error */
+/*#define ENABLE_DEBUG*/
+
+#ifdef ENABLE_DEBUG
+#define DEBUG(x) x
+#else
+#define DEBUG(x)
+#endif
+
+
+#define UNIMPLEMENTED G_STMT_START { g_warning (G_STRLOC": unimplemented"); } G_STMT_END
+
+/* Constant strings for parsing perl script error output */
+#define ERROR_PREFIX "ERROR("
+#define WARNING_PREFIX "WARNING("
+#define MESSAGE_DELIMITER ": "
+
+
+/* ----- Queue data types ----- */
+
+/* FIXME: extend and make most operations asynchronous */
+typedef enum {
+ BUILD
+} GbfAmProjectOpType;
+
+typedef struct {
+ GbfAmProject *project;
+ GbfAmProjectOpType type;
+ gchar *build_id;
+} GbfAmProjectOp;
+
+
+/* ----- Change sets data types ----- */
+
+typedef struct _GbfAmChange GbfAmChange;
+
+typedef enum {
+ GBF_AM_CHANGE_ADDED,
+ GBF_AM_CHANGE_REMOVED
+} GbfAmChangeType;
+
+struct _GbfAmChange {
+ GbfAmChangeType change;
+ GbfAmNodeType type;
+ gchar *id;
+};
+
+
+/* ----- XML output parser data types ----- */
+
+typedef struct _GbfAmProjectParseData GbfAmProjectParseData;
+
+struct _GbfAmProjectParseData {
+ GbfAmProject *project;
+
+ /* For tracking state */
+ GNode *current_node;
+ gint depth; /* group depth only */
+ GbfAmConfigMapping *config;
+ gchar *param_key;
+ gboolean full_report;
+
+ /* parser state */
+ enum {
+ PARSE_INITIAL,
+ PARSE_DONE,
+
+ PARSE_PROJECT,
+ PARSE_GROUP,
+ PARSE_TARGET,
+ PARSE_SOURCE,
+ PARSE_DEPENDENCY,
+ PARSE_CONFIG,
+ PARSE_PARAM,
+ PARSE_ITEM,
+ PARSE_PARAM_DONE,
+
+ PARSE_ERROR
+ } state, save_state;
+
+ /* list of GbfAmChange with changed project elements */
+ gboolean compute_change_set;
+ GSList *change_set;
+
+ /* hash table to keep track of possibly removed nodes */
+ GHashTable *nodes;
+
+ /* Errors accumulation */
+ GString *error;
+};
+
+
+/* ----- Script spawning data types and constants ----- */
+
+#define GBF_AM_PARSE SCRIPTS_DIR "/gbf-am-parse"
+/* timeout in milliseconds */
+#define SCRIPT_TIMEOUT 30000
+
+/* buffer size tuning parameters */
+#define READ_BUFFER_SIZE 32768
+#define READ_BUFFER_DELTA 16384
+
+typedef struct _GbfAmSpawnData GbfAmSpawnData;
+typedef struct _GbfAmChannel GbfAmChannel;
+
+struct _GbfAmChannel {
+ GIOChannel *channel;
+ gchar *buffer;
+ gsize size; /* allocated buffer size */
+ gsize length; /* real buffer length/index into buffer */
+ guint tag; /* main loop source tag for this channel's watch */
+};
+
+struct _GbfAmSpawnData {
+ GMainLoop *main_loop;
+
+ /* child PID to kill it on timeout */
+ gint child_pid;
+
+ /* buffers and related channels */
+ GbfAmChannel input;
+ GbfAmChannel output;
+ GbfAmChannel error;
+ gint open_channels;
+};
+
+
+/* ----- Standard GObject types and variables ----- */
+
+enum {
+ PROP_0,
+ PROP_PROJECT_DIR
+};
+
+static GbfProject *parent_class;
+
+
+/* ----------------------------------------------------------------------
+ Private prototypes
+ ---------------------------------------------------------------------- */
+
+static gboolean uri_is_equal (const gchar *uri1,
+ const gchar *uri2);
+static gboolean uri_is_parent (const gchar *parent_uri,
+ const gchar *child_uri);
+static gboolean uri_is_local_path (const gchar *uri);
+static gchar *uri_to_path (const gchar *uri);
+static gchar *uri_normalize (const gchar *uri,
+ const gchar *base_uri);
+
+static gboolean xml_write_add_source (GbfAmProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *uri);
+static gboolean xml_write_remove_source (GbfAmProject *project,
+ xmlDocPtr doc,
+ GNode *g_node);
+static gboolean xml_write_add_target (GbfAmProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *name,
+ const gchar *type);
+static gboolean xml_write_add_group (GbfAmProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *new_group);
+static xmlDocPtr xml_new_change_doc (GbfAmProject *project);
+
+static GbfAmChange *change_new (GbfAmChangeType ch,
+ GbfAmNode *node);
+static void change_free (GbfAmChange *change);
+static GbfAmChange *change_set_find (GSList *change_set,
+ GbfAmChangeType ch,
+ GbfAmNodeType type);
+static void change_set_destroy (GSList *change_set);
+
+static void error_set (GError **error,
+ gint code,
+ const gchar *message);
+static GError *parse_errors (GbfAmProject *project,
+ const gchar *error_buffer);
+static gboolean parse_output_xml (GbfAmProject *project,
+ gchar *xml,
+ gint length,
+ GSList **change_set,
+ gchar **error_message);
+
+static void spawn_shutdown (GbfAmSpawnData *data);
+static void spawn_data_destroy (GbfAmSpawnData *data);
+static gboolean spawn_write_child (GIOChannel *ioc,
+ GIOCondition condition,
+ gpointer user_data);
+static gboolean spawn_read_output (GIOChannel *ioc,
+ GIOCondition condition,
+ gpointer user_data);
+static gboolean spawn_read_error (GIOChannel *ioc,
+ GIOCondition condition,
+ gpointer user_data);
+static gboolean spawn_kill_child (GbfAmSpawnData *data);
+static GbfAmSpawnData *spawn_script (gchar **argv,
+ gint timeout,
+ gchar *input,
+ gint input_size,
+ GIOFunc input_cb,
+ GIOFunc output_cb,
+ GIOFunc error_cb);
+
+static gboolean project_reload (GbfAmProject *project,
+ GError **err);
+static gboolean project_update (GbfAmProject *project,
+ xmlDocPtr doc,
+ GSList **change_set,
+ GError **err);
+
+static void gbf_am_node_free (GbfAmNode *node);
+static GNode *project_node_new (GbfAmNodeType type);
+static void project_node_destroy (GbfAmProject *project,
+ GNode *g_node);
+static void project_data_destroy (GbfAmProject *project);
+static void project_data_init (GbfAmProject *project);
+
+static void gbf_am_project_class_init (GbfAmProjectClass *klass);
+static void gbf_am_project_instance_init (GbfAmProject *project);
+static void gbf_am_project_dispose (GObject *object);
+static void gbf_am_project_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+/*
+ * Queue operations ------------------------
+ *
+ * - Operations are added to the head and retrieved from the tail
+ */
+
+/*
+ * URI and path manipulation functions -----------------------------
+ */
+
+static gboolean
+uri_is_equal (const gchar *uri1,
+ const gchar *uri2)
+{
+ gboolean retval = FALSE;
+ GnomeVFSURI *vfs_uri1, *vfs_uri2;
+
+ vfs_uri1 = gnome_vfs_uri_new (uri1);
+ vfs_uri2 = gnome_vfs_uri_new (uri2);
+ if (vfs_uri1 != NULL && vfs_uri2 != NULL)
+ retval = gnome_vfs_uri_equal (vfs_uri1, vfs_uri2);
+ gnome_vfs_uri_unref (vfs_uri1);
+ gnome_vfs_uri_unref (vfs_uri2);
+
+ return retval;
+}
+
+static gboolean
+uri_is_parent (const gchar *parent_uri,
+ const gchar *child_uri)
+{
+ gboolean retval = FALSE;
+ GnomeVFSURI *parent, *child;
+
+ parent = gnome_vfs_uri_new (parent_uri);
+ child = gnome_vfs_uri_new (child_uri);
+ if (parent != NULL && child != NULL)
+ retval = gnome_vfs_uri_is_parent (parent, child, TRUE);
+ gnome_vfs_uri_unref (parent);
+ gnome_vfs_uri_unref (child);
+
+ return retval;
+}
+
+static gboolean
+uri_is_local_path (const gchar *uri)
+{
+ const gchar *p;
+
+ for (p = uri;
+ g_ascii_isalnum (*p) || *p == '+' || *p == '-' || *p == '.';
+ p++)
+ ;
+
+ if (*p == ':')
+ return FALSE;
+ else
+ return TRUE;
+}
+
+static gchar *
+uri_to_path (const gchar *uri)
+{
+ return gnome_vfs_get_local_path_from_uri (uri);
+}
+
+static gchar *
+uri_normalize (const gchar *uri, const gchar *base_uri)
+{
+ gchar *normalized_uri = NULL;
+
+ if (uri_is_local_path (uri)) {
+ gchar *absolute_path;
+
+ absolute_path = gnome_vfs_expand_initial_tilde (uri);
+ if (!g_path_is_absolute (uri)) {
+ gchar *tmp = absolute_path;
+ gchar *base_dir;
+ if (base_uri != NULL)
+ base_dir = uri_to_path (base_uri);
+ else
+ base_dir = g_get_current_dir ();
+
+ absolute_path = g_build_filename (base_dir, tmp, NULL);
+ g_free (base_dir);
+ g_free (tmp);
+ }
+ normalized_uri = gnome_vfs_make_uri_canonical (absolute_path);
+ g_free (absolute_path);
+ } else {
+ GnomeVFSURI *vfs_uri;
+
+ vfs_uri = gnome_vfs_uri_new (uri);
+ /* FIXME: this should probably check that the method is file: */
+ if (gnome_vfs_uri_is_local (vfs_uri))
+ normalized_uri = gnome_vfs_make_uri_canonical (uri);
+ gnome_vfs_uri_unref (vfs_uri);
+ }
+
+ /* strip trailing slash */
+ if (normalized_uri) {
+ gint length = strlen (normalized_uri);
+ gchar *p;
+ if (length > 0) {
+ p = normalized_uri + length - 1;
+ if (*p == '/')
+ *p = '\0';
+ }
+ }
+
+ return normalized_uri;
+}
+
+/**
+ * uri_get_chrooted_path:
+ * @root_uri: the root uri (or NULL)
+ * @uri: the uri which must be inside @root_uri for which the
+ * root-changed path is wanted
+ *
+ * E.g.: uri_get_chrooted_path ("file:///foo/bar", "file:///foo/bar/baz/winkle") -> "/baz/winkle"
+ *
+ * Return value: a newly allocated chrooted path
+ **/
+static gchar *
+uri_get_chrooted_path (const gchar *root_uri, const gchar *uri)
+{
+ gchar *root_path, *path;
+ gint root_length;
+ gchar *retval = NULL;
+
+ path = uri_to_path (uri);
+ if (root_uri == NULL)
+ return path;
+
+ root_path = uri_to_path (root_uri);
+
+ root_length = strlen (root_path);
+ if (strncmp (root_path, path, root_length) == 0) {
+ /* check for trailing path separator in root... we need it to
+ * make the returned path absolute */
+ if (root_path [root_length - 1] == '/')
+ root_length--;
+ retval = g_strdup (path + root_length);
+ }
+
+ g_free (root_path);
+ g_free (path);
+
+ return retval;
+}
+
+/*
+ * Project modification functions -----------------------------
+ */
+
+static xmlNodePtr
+xml_new_source_node (GbfAmProject *project,
+ xmlDocPtr doc,
+ const gchar *uri)
+{
+ xmlNodePtr source;
+ gchar *filename;
+
+ source = xmlNewDocNode (doc, NULL, BAD_CAST "source", NULL);
+ filename = uri_get_chrooted_path (project->project_root_uri, uri);
+ xmlSetProp (source, BAD_CAST "uri", BAD_CAST filename);
+ g_free (filename);
+
+ return source;
+}
+
+static xmlNodePtr
+xml_write_location_recursive (GbfAmProject *project,
+ xmlDocPtr doc,
+ xmlNodePtr cur,
+ GNode *g_node)
+{
+ xmlNodePtr result, xml_node, last_node;
+ gboolean finished = FALSE;
+
+ result = NULL;
+ last_node = NULL;
+ xml_node = NULL;
+ while (g_node && !finished) {
+ GbfAmNode *node = GBF_AM_NODE (g_node);
+ switch (node->type) {
+ case GBF_AM_NODE_GROUP:
+ xml_node = xmlNewDocNode (doc, NULL, BAD_CAST "group", NULL);
+ xmlSetProp (xml_node, BAD_CAST "id", BAD_CAST node->id);
+ finished = TRUE;
+ break;
+ case GBF_AM_NODE_TARGET:
+ xml_node = xmlNewDocNode (doc, NULL, BAD_CAST "target", NULL);
+ /* strip the group id from the target
+ id, since the script identifies
+ targets only within the group */
+ xmlSetProp (xml_node, BAD_CAST "id",
+ BAD_CAST node->id +
+ strlen (GBF_AM_NODE (g_node->parent)->id));
+ break;
+ case GBF_AM_NODE_SOURCE:
+ xml_node = xml_new_source_node (project, doc, node->uri);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ /* set returned node */
+ if (result == NULL)
+ result = xml_node;
+
+ /* link the previously created node to the new node */
+ if (last_node != NULL)
+ xmlAddChild (xml_node, last_node);
+
+ last_node = xml_node;
+ g_node = g_node->parent;
+ }
+ /* link the last created node to the current node */
+ xmlAddChild (cur, last_node);
+
+ return result;
+}
+
+static gboolean
+xml_write_add_source (GbfAmProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *uri)
+{
+ xmlNodePtr cur, source;
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST "add", NULL);
+ xmlSetProp (cur, BAD_CAST "type", BAD_CAST "source");
+ xmlAddChild (doc->children, cur);
+
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+ source = xml_new_source_node (project, doc, uri);
+ xmlAddChild (cur, source);
+
+ return (cur != NULL);
+}
+
+static gboolean
+xml_write_remove_source (GbfAmProject *project,
+ xmlDocPtr doc,
+ GNode *g_node)
+{
+ xmlNodePtr cur;
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST "remove", NULL);
+ xmlSetProp (cur, BAD_CAST "type", BAD_CAST "source");
+ xmlAddChild (doc->children, cur);
+
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+
+ return (cur != NULL);
+}
+
+static gboolean
+xml_write_add_target (GbfAmProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *name,
+ const gchar *type)
+{
+ xmlNodePtr cur, target;
+
+ g_assert (GBF_AM_NODE (g_node)->type == GBF_AM_NODE_GROUP);
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST "add", NULL);
+ xmlSetProp (cur, BAD_CAST "type", BAD_CAST "target");
+ xmlAddChild (doc->children, cur);
+
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+
+ target = xmlNewDocNode (doc, NULL, BAD_CAST "target", NULL);
+ xmlSetProp (target, BAD_CAST "id", BAD_CAST name);
+ xmlSetProp (target, BAD_CAST "type", BAD_CAST type);
+ xmlAddChild (cur, target);
+
+ return TRUE;
+}
+
+typedef struct {
+ GbfAmConfigMapping *old_config;
+ xmlDocPtr doc;
+ xmlNodePtr curr_xml_node;
+} GbfXmlWriteData;
+
+static void
+xml_write_set_item_config_cb (const gchar *param, GbfAmConfigValue *value,
+ gpointer user_data)
+{
+ xmlNodePtr param_node;
+ GbfXmlWriteData *data = (GbfXmlWriteData*)user_data;
+
+ if (value->type == GBF_AM_TYPE_STRING) {
+ GbfAmConfigValue *old_value = NULL;
+ const gchar *old_str = "", *new_str = "";
+
+ new_str = gbf_am_config_value_get_string (value);
+ if (new_str == NULL)
+ new_str = "";
+ if (data->old_config)
+ old_value = gbf_am_config_mapping_lookup (data->old_config, param);
+ if (old_value) {
+ old_str = gbf_am_config_value_get_string (old_value);
+ if (old_str == NULL)
+ old_str = "";
+ }
+ if (strcmp (new_str, old_str) != 0)
+ {
+ param_node = xmlNewDocNode (data->doc, NULL,
+ BAD_CAST "item", NULL);
+ xmlSetProp (param_node, BAD_CAST "name", BAD_CAST param);
+ xmlSetProp (param_node, BAD_CAST "value", BAD_CAST new_str);
+ xmlAddChild (data->curr_xml_node, param_node);
+ }
+ }
+}
+
+static void
+xml_write_set_param_config_cb (const gchar *param, GbfAmConfigValue *value,
+ gpointer user_data)
+{
+ xmlNodePtr param_node;
+ GbfXmlWriteData *data = (GbfXmlWriteData*)user_data;
+
+ if (value->type == GBF_AM_TYPE_STRING) {
+ GbfAmConfigValue *old_value;
+ const gchar *old_str = "", *new_str = "";
+
+ new_str = gbf_am_config_value_get_string (value);
+ if (new_str == NULL)
+ new_str = "";
+
+ old_value = gbf_am_config_mapping_lookup (data->old_config, param);
+ if (old_value) {
+ old_str = gbf_am_config_value_get_string (old_value);
+ if (old_str == NULL)
+ old_str = "";
+ }
+ if (strcmp (new_str, old_str) != 0)
+ {
+ param_node = xmlNewDocNode (data->doc, NULL,
+ BAD_CAST "param", NULL);
+ xmlSetProp (param_node, BAD_CAST "name", BAD_CAST param);
+ xmlSetProp (param_node, BAD_CAST "value", BAD_CAST new_str);
+ xmlAddChild (data->curr_xml_node, param_node);
+ }
+ } else if (value->type == GBF_AM_TYPE_LIST) {
+ param_node = xmlNewDocNode (data->doc, NULL,
+ BAD_CAST "param", NULL);
+ xmlSetProp (param_node, BAD_CAST "name", BAD_CAST param);
+ /* FIXME */
+ } else if (value->type == GBF_AM_TYPE_MAPPING) {
+
+ GbfXmlWriteData write_data;
+ GbfAmConfigValue *old_value = NULL;
+ GbfAmConfigMapping *new_mapping = NULL, *old_mapping = NULL;
+
+ new_mapping = gbf_am_config_value_get_mapping (value);
+ old_value = gbf_am_config_mapping_lookup (data->old_config, param);
+ if (old_value)
+ old_mapping = gbf_am_config_value_get_mapping (old_value);
+
+ param_node = xmlNewDocNode (data->doc, NULL, BAD_CAST "param", NULL);
+
+ xmlSetProp (param_node, BAD_CAST "name", BAD_CAST param);
+
+ write_data.doc = data->doc;
+ write_data.curr_xml_node = param_node;
+ write_data.old_config = old_mapping;
+
+ gbf_am_config_mapping_foreach (new_mapping,
+ xml_write_set_item_config_cb,
+ &write_data);
+ if (param_node->children)
+ xmlAddChild (data->curr_xml_node, param_node);
+ else
+ xmlFreeNode (param_node);
+ } else {
+ g_warning ("Should not be here");
+ }
+}
+
+static gboolean
+xml_write_set_config (GbfAmProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ GbfAmConfigMapping *new_config)
+{
+ xmlNodePtr cur, config;
+ GbfXmlWriteData user_data;
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST "set", NULL);
+ xmlSetProp (cur, BAD_CAST "type", BAD_CAST "config");
+ xmlAddChild (doc->children, cur);
+
+ if (g_node)
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+
+ config = xmlNewDocNode (doc, NULL, BAD_CAST "config", NULL);
+ xmlAddChild (cur, config);
+
+ user_data.doc = doc;
+ user_data.curr_xml_node = config;
+ user_data.old_config = g_node? GBF_AM_NODE (g_node)->config : project->project_config;
+
+ gbf_am_config_mapping_foreach (new_config,
+ xml_write_set_param_config_cb,
+ &user_data);
+ if (config->children)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static gboolean
+xml_write_remove_target (GbfAmProject *project,
+ xmlDocPtr doc,
+ GNode *g_node)
+{
+ xmlNodePtr cur;
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST "remove", NULL);
+ xmlSetProp (cur, BAD_CAST "type", BAD_CAST "target");
+ xmlAddChild (doc->children, cur);
+
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+
+ return (cur != NULL);
+}
+
+static gboolean
+xml_write_add_group (GbfAmProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *new_group)
+{
+ xmlNodePtr cur, group;
+ gchar *new_id;
+
+ g_assert (GBF_AM_NODE (g_node)->type == GBF_AM_NODE_GROUP);
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST "add", NULL);
+ xmlSetProp (cur, BAD_CAST "type", BAD_CAST "group");
+ xmlAddChild (doc->children, cur);
+
+ /* calculate new id needed by the script */
+ new_id = g_strdup_printf ("%s%s/", GBF_AM_NODE (g_node)->id, new_group);
+
+ group = xmlNewDocNode (doc, NULL, BAD_CAST "group", NULL);
+ xmlSetProp (group, BAD_CAST "id", BAD_CAST new_id);
+ xmlAddChild (cur, group);
+ g_free (new_id);
+
+ return TRUE;
+}
+
+static gboolean
+xml_write_remove_group (GbfAmProject *project,
+ xmlDocPtr doc,
+ GNode *g_node)
+{
+ xmlNodePtr cur;
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST "remove", NULL);
+ xmlSetProp (cur, BAD_CAST "type", BAD_CAST "group");
+ xmlAddChild (doc->children, cur);
+
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+
+ return (cur != NULL);
+}
+
+static xmlDocPtr
+xml_new_change_doc (GbfAmProject *project)
+{
+ xmlDocPtr doc;
+
+ doc = xmlNewDoc (BAD_CAST "1.0");
+ if (doc != NULL) {
+ gchar *root_path;
+ root_path = uri_to_path (project->project_root_uri);
+ doc->children = xmlNewDocNode (doc, NULL, BAD_CAST "project", NULL);
+ xmlSetProp (doc->children, BAD_CAST "root", BAD_CAST root_path);
+ g_free (root_path);
+ }
+
+ return doc;
+}
+
+
+/*
+ * File monitoring support --------------------------------
+ * FIXME: review these
+ */
+
+static void
+monitor_cb (GnomeVFSMonitorHandle *handle,
+ const gchar *monitor_uri,
+ const gchar *info_uri,
+ GnomeVFSMonitorEventType event_type,
+ gpointer data)
+{
+ GbfAmProject *project = data;
+
+ g_return_if_fail (project != NULL && GBF_IS_AM_PROJECT (project));
+
+ switch (event_type) {
+ case GNOME_VFS_MONITOR_EVENT_CHANGED:
+ case GNOME_VFS_MONITOR_EVENT_DELETED:
+ /* monitor will be removed here... is this safe? */
+ DEBUG (g_message ("File changed"));
+ project_reload (project, NULL);
+ g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+monitor_add (GbfAmProject *project, const gchar *uri)
+{
+ GnomeVFSMonitorHandle *handle = NULL;
+ GnomeVFSResult result;
+
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (project->monitors != NULL);
+
+ if (!uri)
+ return;
+
+ handle = g_hash_table_lookup (project->monitors, uri);
+ if (!handle) {
+ GnomeVFSURI *vfs_uri;
+ gboolean exists;
+
+ vfs_uri = gnome_vfs_uri_new (uri);
+ exists = gnome_vfs_uri_exists (vfs_uri);
+ gnome_vfs_uri_unref (vfs_uri);
+
+ if (exists) {
+ result = gnome_vfs_monitor_add (&handle,
+ uri,
+ GNOME_VFS_MONITOR_FILE,
+ monitor_cb,
+ project);
+ if (result == GNOME_VFS_OK) {
+ g_hash_table_insert (project->monitors,
+ g_strdup (uri),
+ handle);
+ }
+ }
+ }
+}
+
+static void
+monitors_remove (GbfAmProject *project)
+{
+ g_return_if_fail (project != NULL);
+
+ if (project->monitors)
+ g_hash_table_destroy (project->monitors);
+ project->monitors = NULL;
+}
+
+static void
+group_hash_foreach_monitor (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GNode *group_node = value;
+ GbfAmProject *project = user_data;
+
+ monitor_add (project, GBF_AM_NODE (group_node)->uri);
+}
+
+static void
+monitors_setup (GbfAmProject *project)
+{
+ g_return_if_fail (project != NULL);
+
+ monitors_remove (project);
+
+ /* setup monitors hash */
+ project->monitors = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) gnome_vfs_monitor_cancel);
+
+ monitor_add (project, project->project_file);
+ g_hash_table_foreach (project->groups, group_hash_foreach_monitor, project);
+}
+
+
+/*
+ * Change sets functions --------------------------------
+ */
+
+static GbfAmChange *
+change_new (GbfAmChangeType ch, GbfAmNode *node)
+{
+ GbfAmChange *change;
+
+ change = g_new0 (GbfAmChange, 1);
+ change->change = ch;
+ change->type = node->type;
+ change->id = g_strdup (node->id);
+
+ return change;
+}
+
+static void
+change_free (GbfAmChange *change)
+{
+ if (change) {
+ g_free (change->id);
+ g_free (change);
+ }
+}
+
+static GbfAmChange *
+change_set_find (GSList *change_set, GbfAmChangeType ch, GbfAmNodeType type)
+{
+ GSList *iter = change_set;
+
+ while (iter) {
+ GbfAmChange *change = iter->data;
+ if (change->change == ch && change->type == type)
+ return change;
+ iter = g_slist_next (iter);
+ }
+
+ return NULL;
+}
+
+static void
+change_set_destroy (GSList *change_set)
+{
+ GSList *l;
+ for (l = change_set; l; l = g_slist_next (l))
+ change_free (l->data);
+ g_slist_free (change_set);
+}
+
+#ifdef ENABLE_DEBUG
+static void
+change_set_debug_print (GSList *change_set)
+{
+ GSList *iter = change_set;
+
+ g_print ("Change set:\n");
+ while (iter) {
+ GbfAmChange *change = iter->data;
+
+ switch (change->change) {
+ case GBF_AM_CHANGE_ADDED:
+ g_print ("added ");
+ break;
+ case GBF_AM_CHANGE_REMOVED:
+ g_print ("removed ");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ switch (change->type) {
+ case GBF_AM_NODE_GROUP:
+ g_print ("group ");
+ break;
+ case GBF_AM_NODE_TARGET:
+ g_print ("target ");
+ break;
+ case GBF_AM_NODE_SOURCE:
+ g_print ("source ");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ g_print ("%s\n", change->id);
+
+ iter = g_slist_next (iter);
+ }
+}
+#endif
+
+/*
+ * Perl script output parser ------------------------------
+ */
+
+#define PARSER_ASSERT(x) G_STMT_START { \
+ if (!(x)) { \
+ DEBUG (g_warning ("Parser assertion at " G_STRLOC " failed: " #x)); \
+ data->state = PARSE_ERROR; return; \
+ } \
+ \
+ } G_STMT_END
+
+static void
+sax_start_element (void *ctxt, const xmlChar *name, const xmlChar **attrs)
+{
+ GbfAmProjectParseData *data;
+ GbfAmProject *project;
+ GNode *g_node;
+ GbfAmNode *node;
+
+ data = ctxt;
+ project = data->project;
+
+ PARSER_ASSERT (data->state != PARSE_ERROR && data->state != PARSE_DONE);
+
+ if (xmlStrEqual (name, BAD_CAST "project")) {
+ const xmlChar *project_file = NULL;
+
+ /* project node */
+ PARSER_ASSERT (data->state == PARSE_INITIAL);
+ data->state = PARSE_PROJECT;
+
+ /* process attributes: lookup project file and check
+ * if we're getting the whole project or just changed
+ * groups */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "root")) {
+ /* FIXME: check that the root is the
+ * same as the project's root */
+ }
+ else if (xmlStrEqual (*attrs, BAD_CAST "report"))
+ data->full_report = (xmlStrEqual (*val, BAD_CAST "full") != 0);
+ else if (xmlStrEqual (*attrs, BAD_CAST "source"))
+ project_file = *val;
+
+ attrs = ++val;
+ }
+
+ /* clear project if we are getting a full report */
+ if (data->full_report) {
+ /* FIXME: will this be correct in all cases? */
+ data->compute_change_set = FALSE;
+ project_data_init (project);
+ }
+
+ /* assign here, since we destroy the data in project_data_init() */
+ if (project_file) {
+ g_free (project->project_file);
+ project->project_file = g_strdup ((char *) project_file);
+ }
+
+ } else if (xmlStrEqual (name, BAD_CAST "group")) {
+ const xmlChar *group_name = NULL;
+ const xmlChar *group_id = NULL;
+ const xmlChar *group_source = NULL;
+
+ /* group node */
+ PARSER_ASSERT (data->state == PARSE_PROJECT ||
+ data->state == PARSE_GROUP);
+ data->state = PARSE_GROUP;
+
+ /* process attributes: lookup name, id and source file
+ * (i.e. Makefile.am) */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "name"))
+ group_name = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "id"))
+ group_id = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "source"))
+ group_source = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (group_name != NULL && group_id != NULL);
+
+ /* lookup the node first, as it could already exist */
+ g_node = g_hash_table_lookup (project->groups, group_id);
+ if (g_node == NULL) {
+ g_node = project_node_new (GBF_AM_NODE_GROUP);
+ node = GBF_AM_NODE (g_node);
+
+ node->id = g_strdup ((char *) group_id);
+
+ /* set parent group */
+ if (data->current_node != NULL) {
+ g_node_prepend (data->current_node, g_node);
+ } else {
+ /* node is project root */
+ g_assert (project->root_node == NULL);
+ project->root_node = g_node;
+ }
+
+ if (data->compute_change_set)
+ data->change_set = g_slist_prepend (
+ data->change_set,
+ change_new (GBF_AM_CHANGE_ADDED, node));
+
+ /* save the group in the hash for quick access */
+ g_hash_table_insert (project->groups, g_strdup (node->id), g_node);
+
+ } else {
+ GNode *child;
+
+ node = GBF_AM_NODE (g_node);
+
+ /* node found so remove it from the tracking hash */
+ g_hash_table_remove (data->nodes, g_node);
+
+ /* re-set data */
+ g_free (node->name);
+ g_free (node->uri);
+
+ /* recreate the configuration */
+ gbf_am_config_mapping_destroy (node->config);
+
+ /* track all child nodes */
+ child = g_node_first_child (g_node);
+ while (child != NULL) {
+ /* only track groups if getting a full
+ * report, otherwise non-changed
+ * groups are later removed from the
+ * project because the script doesn't
+ * report them */
+ if (data->full_report ||
+ GBF_AM_NODE (child)->type != GBF_AM_NODE_GROUP)
+ g_hash_table_insert (data->nodes, child, child);
+ child = g_node_next_sibling (child);
+ }
+ }
+ node->name = g_strdup ((char *) group_name);
+ node->uri = g_strdup ((char *) group_source);
+ node->config = gbf_am_config_mapping_new ();
+
+ /* set working node */
+ data->depth++;
+ data->current_node = g_node;
+
+ } else if (xmlStrEqual (name, BAD_CAST "target")) {
+ const xmlChar *id = NULL;
+ const xmlChar *target_name = NULL;
+ const xmlChar *target_type = NULL;
+ gchar *group_id;
+
+ /* target node */
+ PARSER_ASSERT (data->state == PARSE_GROUP);
+ data->state = PARSE_TARGET;
+
+ /* process attributes: lookup name, type and id */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "name"))
+ target_name = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "type"))
+ target_type = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "id"))
+ id = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (target_name != NULL && target_type != NULL);
+
+ /* compute the id using the given by the script if possible */
+ group_id = g_strdup_printf ("%s%s",
+ GBF_AM_NODE (data->current_node)->id,
+ id != NULL ? id : target_name);
+
+ /* lookup the node first, as it could already exist */
+ g_node = g_hash_table_lookup (project->targets, group_id);
+ if (g_node == NULL) {
+ g_node = project_node_new (GBF_AM_NODE_TARGET);
+ node = GBF_AM_NODE (g_node);
+
+ node->id = group_id;
+
+ /* set target's parent */
+ g_node_prepend (data->current_node, g_node);
+
+ if (data->compute_change_set)
+ data->change_set = g_slist_prepend (
+ data->change_set,
+ change_new (GBF_AM_CHANGE_ADDED, node));
+
+ /* save the target in the hash for quick access */
+ g_hash_table_insert (project->targets, g_strdup (node->id), g_node);
+
+ } else {
+ GNode *child;
+
+ node = GBF_AM_NODE (g_node);
+ g_free (group_id);
+
+ /* node found so remove it from the tracking hash */
+ g_hash_table_remove (data->nodes, g_node);
+
+ /* re-set data */
+ g_free (node->name);
+ g_free (node->detail);
+
+ /* recreate the configuration */
+ gbf_am_config_mapping_destroy (node->config);
+
+ /* track all child nodes */
+ child = g_node_first_child (g_node);
+ while (child != NULL) {
+ g_hash_table_insert (data->nodes, child, child);
+ child = g_node_next_sibling (child);
+ }
+ }
+ node->name = g_strdup ((char *) target_name);
+ node->detail = g_strdup ((char *) target_type);
+ node->config = gbf_am_config_mapping_new ();
+
+ /* set working node */
+ data->current_node = g_node;
+
+ } else if (xmlStrEqual (name, BAD_CAST "source")) {
+ const xmlChar *uri = NULL;
+ gchar *source_uri, *source_id;
+
+ /* source node */
+ PARSER_ASSERT (data->state == PARSE_TARGET);
+ data->state = PARSE_SOURCE;
+
+ /* process attributes: lookup uri */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "uri"))
+ uri = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (uri != NULL);
+
+ source_uri = g_build_filename (project->project_root_uri, uri, NULL);
+ source_id = g_strdup_printf ("%s:%s",
+ GBF_AM_NODE (data->current_node)->id,
+ source_uri);
+
+ /* lookup the node first, as it could already exist */
+ g_node = g_hash_table_lookup (project->sources, source_id);
+ if (g_node == NULL) {
+ g_node = project_node_new (GBF_AM_NODE_SOURCE);
+ node = GBF_AM_NODE (g_node);
+
+ node->id = source_id;
+
+ /* set source's parent */
+ g_node_prepend (data->current_node, g_node);
+
+ if (data->compute_change_set)
+ data->change_set = g_slist_prepend (
+ data->change_set,
+ change_new (GBF_AM_CHANGE_ADDED, node));
+
+ /* save the source in the hash for quick access */
+ g_hash_table_insert (project->sources, g_strdup (node->id), g_node);
+
+ } else {
+ /* node found so remove it from the tracking hash */
+ g_hash_table_remove (data->nodes, g_node);
+
+ node = GBF_AM_NODE (g_node);
+ g_free (source_id);
+
+ /* re-set data */
+ g_free (node->uri);
+ }
+ node->uri = source_uri;
+
+ /* set working node */
+ data->current_node = g_node;
+
+ } else if (xmlStrEqual (name, BAD_CAST "dependency")) {
+ const xmlChar *uri = NULL;
+ const xmlChar *target_dep = NULL;
+ gchar *source_uri, *source_id;
+
+ /* dependency node */
+ PARSER_ASSERT (data->state == PARSE_TARGET);
+ data->state = PARSE_DEPENDENCY;
+
+ /* process attributes: lookup file and dependency target */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "file"))
+ uri = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "target"))
+ target_dep = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (uri != NULL && target_dep != NULL);
+
+ /* Compute the id */
+ source_uri = g_build_filename (project->project_root_uri, uri, NULL);
+ source_id = g_strdup_printf ("%s:%s",
+ GBF_AM_NODE (data->current_node)->id,
+ source_uri);
+
+ /* lookup the node first, as it could already exist */
+ g_node = g_hash_table_lookup (project->sources, source_id);
+ if (g_node == NULL) {
+ g_node = project_node_new (GBF_AM_NODE_SOURCE);
+ node = GBF_AM_NODE (g_node);
+
+ node->id = source_id;
+
+ /* set source's parent */
+ g_node_prepend (data->current_node, g_node);
+
+ if (data->compute_change_set)
+ data->change_set = g_slist_prepend (
+ data->change_set,
+ change_new (GBF_AM_CHANGE_ADDED, node));
+
+ /* save the source in the hash for quick access */
+ g_hash_table_insert (project->sources, g_strdup (node->id), g_node);
+
+ } else {
+ /* node found so remove it from the tracking hash */
+ g_hash_table_remove (data->nodes, g_node);
+
+ g_free (source_id);
+ node = GBF_AM_NODE (g_node);
+
+ /* re-set data */
+ g_free (node->uri);
+ g_free (node->detail);
+ }
+ node->uri = source_uri;
+ node->detail = g_strdup ((char *) target_dep);
+
+ /* set working node */
+ data->current_node = g_node;
+
+ } else if (xmlStrEqual (name, BAD_CAST "config")) {
+ /* config node */
+ PARSER_ASSERT (data->state == PARSE_PROJECT ||
+ data->state == PARSE_GROUP ||
+ data->state == PARSE_TARGET);
+
+ switch (data->state) {
+ case PARSE_PROJECT:
+ data->config = project->project_config;
+ break;
+ case PARSE_GROUP:
+ case PARSE_TARGET:
+ g_assert (data->current_node != NULL);
+ data->config = GBF_AM_NODE (data->current_node)->config;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ data->save_state = data->state;
+ data->state = PARSE_CONFIG;
+
+ } else if (xmlStrEqual (name, BAD_CAST "param")) {
+ const xmlChar *param_name = NULL;
+ const xmlChar *param_value = NULL;
+
+ /* config param node */
+ PARSER_ASSERT (data->state == PARSE_CONFIG);
+
+ /* process attributes: lookup parameter name and value (if scalar) */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "name"))
+ param_name = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "value"))
+ param_value = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (param_name != NULL);
+
+ if (param_value != NULL) {
+ GbfAmConfigValue *param;
+
+ /* save scalar string parameter */
+ param = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+ gbf_am_config_value_set_string (param, (char *) param_value);
+
+ /* try to save the parameter in the config mapping */
+ if (!gbf_am_config_mapping_update (data->config,
+ (char *) param_name, param)) {
+ gbf_am_config_value_free (param);
+ }
+
+ /* we are finished with the parameter */
+ data->state = PARSE_PARAM_DONE;
+
+ } else {
+ /* we expect a list or a hash of values */
+ data->param_key = g_strdup ((char *) param_name);
+ data->state = PARSE_PARAM;
+ }
+
+ } else if (xmlStrEqual (name, BAD_CAST "item")) {
+ GbfAmConfigValue *param, *item;
+ const xmlChar *item_name = NULL;
+ const xmlChar *item_value = NULL;
+
+ /* parameter item node */
+ PARSER_ASSERT (data->state == PARSE_PARAM);
+ g_assert (data->param_key != NULL);
+ data->state = PARSE_ITEM;
+
+ /* process attributes: lookup parameter name and value (if scalar) */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "name"))
+ item_name = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "value"))
+ item_value = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (item_value != NULL);
+
+ /* create item value */
+ item = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+ gbf_am_config_value_set_string (item, (char *) item_value);
+
+ /* get current configuration parameter */
+ param = gbf_am_config_mapping_lookup (data->config, data->param_key);
+
+ /* if this is the first item, we must decide whether
+ it's a list or a mapping. if it's not the first
+ one, we must check that the items are of the same
+ type */
+ if (param == NULL) {
+ if (item_name != NULL) {
+ param = gbf_am_config_value_new (GBF_AM_TYPE_MAPPING);
+ } else {
+ param = gbf_am_config_value_new (GBF_AM_TYPE_LIST);
+ }
+ gbf_am_config_mapping_insert (data->config,
+ data->param_key,
+ param);
+ }
+
+ switch (param->type) {
+ case GBF_AM_TYPE_LIST:
+ param->list = g_slist_prepend (param->list, item);
+ break;
+
+ case GBF_AM_TYPE_MAPPING:
+ if (item_name == NULL ||
+ !gbf_am_config_mapping_update (param->mapping,
+ (char *) item_name,
+ item)) {
+ gbf_am_config_value_free (item);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+}
+
+static void
+sax_end_element (void *ctx, const xmlChar *name)
+{
+ GbfAmProjectParseData *data = ctx;
+
+ PARSER_ASSERT (data->state != PARSE_ERROR && data->state != PARSE_DONE);
+
+ if (xmlStrEqual (name, BAD_CAST "project")) {
+ PARSER_ASSERT (data->state == PARSE_PROJECT);
+ g_assert (data->current_node == NULL);
+ data->state = PARSE_DONE;
+
+ } else if (xmlStrEqual (name, BAD_CAST "group")) {
+ PARSER_ASSERT (data->state == PARSE_GROUP);
+ g_assert (data->current_node != NULL);
+ data->depth--;
+ if (data->depth == 0) {
+ data->current_node = NULL;
+ data->state = PARSE_PROJECT;
+ } else {
+ data->current_node = data->current_node->parent;
+ }
+
+ /* FIXME: resolve all target inter-dependencies here */
+
+ } else if (xmlStrEqual (name, BAD_CAST "target")) {
+ PARSER_ASSERT (data->state == PARSE_TARGET);
+ g_assert (data->current_node != NULL);
+ data->current_node = data->current_node->parent;
+ data->state = PARSE_GROUP;
+
+ } else if (xmlStrEqual (name, BAD_CAST "source")) {
+ PARSER_ASSERT (data->state == PARSE_SOURCE);
+ g_assert (data->current_node != NULL);
+ data->current_node = data->current_node->parent;
+ data->state = PARSE_TARGET;
+
+ } else if (xmlStrEqual (name, BAD_CAST "dependency")) {
+ PARSER_ASSERT (data->state == PARSE_DEPENDENCY);
+ g_assert (data->current_node != NULL);
+ data->current_node = data->current_node->parent;
+ data->state = PARSE_TARGET;
+
+ } else if (xmlStrEqual (name, BAD_CAST "config")) {
+ PARSER_ASSERT (data->state == PARSE_CONFIG);
+ data->state = data->save_state;
+ data->save_state = PARSE_INITIAL;
+ data->config = NULL;
+
+ } else if (xmlStrEqual (name, BAD_CAST "param")) {
+ PARSER_ASSERT (data->state == PARSE_PARAM ||
+ data->state == PARSE_PARAM_DONE);
+ data->state = PARSE_CONFIG;
+ g_free (data->param_key);
+ data->param_key = NULL;
+
+ } else if (xmlStrEqual (name, BAD_CAST "item")) {
+ PARSER_ASSERT (data->state == PARSE_ITEM);
+ data->state = PARSE_PARAM;
+
+ }
+}
+
+static void
+sax_error (void *ctx, const char *format, ...)
+{
+ va_list args;
+ char buffer[256];
+
+ va_start (args, format);
+ vsnprintf (buffer, 256, format, args);
+ va_end (args);
+
+ GbfAmProjectParseData *data = ctx;
+ g_string_append (data->error, buffer);
+}
+
+#undef PARSER_ASSERT
+
+static void
+hash_foreach_add_removed (GNode *g_node,
+ gpointer value,
+ GSList **change_set)
+{
+ *change_set = g_slist_prepend (*change_set,
+ change_new (GBF_AM_CHANGE_REMOVED,
+ GBF_AM_NODE (g_node)));
+}
+
+static void
+hash_foreach_destroy_node (GNode *g_node,
+ gpointer value,
+ GbfAmProject *project)
+{
+ project_node_destroy (project, g_node);
+}
+
+static gboolean
+parse_output_xml (GbfAmProject *project,
+ gchar *xml_text,
+ gint length,
+ GSList **change_set,
+ gchar ** error_message)
+{
+ xmlSAXHandler handler;
+ GbfAmProjectParseData data;
+ int retval;
+
+ memset (&handler, 0, sizeof (xmlSAXHandler));
+ handler.startElement = sax_start_element;
+ handler.endElement = sax_end_element;
+ handler.error = sax_error;
+ handler.initialized = 0;
+
+ data.state = PARSE_INITIAL;
+ data.save_state = PARSE_INITIAL;
+ data.project = project;
+ data.current_node = NULL;
+ data.depth = 0;
+ data.config = NULL;
+ data.param_key = NULL;
+ data.full_report = TRUE;
+
+ data.change_set = NULL;
+ data.nodes = g_hash_table_new (g_direct_hash, g_direct_equal);
+ data.compute_change_set = (change_set != NULL);
+ data.error = g_string_new (NULL);
+
+ xmlSubstituteEntitiesDefault (TRUE);
+
+ /* Parse document */
+ retval = xmlSAXUserParseMemory (&handler, &data, xml_text, length);
+
+ if (retval != 0)
+ {
+ if (error_message) {
+ if (data.error->len > 0)
+ *error_message = g_strdup (data.error->str);
+ }
+ }
+ g_string_free (data.error, TRUE);
+
+ if (data.state != PARSE_DONE)
+ {
+ retval = -1;
+ }
+
+ /* construct the change set */
+ if (retval >= 0 && data.compute_change_set) {
+ g_hash_table_foreach (data.nodes,
+ (GHFunc) hash_foreach_add_removed,
+ &data.change_set);
+ *change_set = data.change_set;
+ data.change_set = NULL;
+ }
+
+ /* free up any remaining parsing data */
+ change_set_destroy (data.change_set);
+ if (data.nodes) {
+ g_hash_table_foreach (data.nodes,
+ (GHFunc) hash_foreach_destroy_node,
+ project);
+ g_hash_table_destroy (data.nodes);
+ }
+ g_free (data.param_key);
+
+ return (retval == 0);
+}
+
+
+/*
+ * Perl script error parsing ------------------------
+ */
+
+/* FIXME: set correct error codes in all the places we call this function */
+static void
+error_set (GError **error, gint code, const gchar *message)
+{
+ if (error != NULL) {
+ if (*error != NULL) {
+ gchar *tmp;
+
+ /* error already created, just change the code
+ * and prepend the string */
+ (*error)->code = code;
+ tmp = (*error)->message;
+ (*error)->message = g_strconcat (message, "\n\n", tmp, NULL);
+ g_free (tmp);
+
+ } else {
+ *error = g_error_new_literal (GBF_PROJECT_ERROR,
+ code,
+ message);
+ }
+ }
+}
+
+static GError *
+parse_errors (GbfAmProject *project,
+ const gchar *error_buffer)
+{
+ const gchar *line_ptr, *next_line, *p;
+ GError *err = NULL;
+ GString *message;
+
+ message = g_string_new (NULL);
+ line_ptr = error_buffer;
+ while (line_ptr) {
+ /* FIXME: maybe use constants or enums for the error type */
+ gint error_type = 0;
+ gint error_code;
+ gint line_length;
+
+ next_line = g_strstr_len (line_ptr, strlen (line_ptr), "\n");
+ /* skip newline */
+ next_line = next_line ? next_line + 1 : NULL;
+ line_length = next_line ? next_line - line_ptr : strlen (line_ptr);
+ p = line_ptr;
+
+ if (g_str_has_prefix (line_ptr, ERROR_PREFIX)) {
+ /* get the error */
+ p = line_ptr + strlen (ERROR_PREFIX);
+ error_type = 1;
+ }
+#if 0
+ /* FIXME: skip warnings for now */
+ else if (g_str_has_prefix (line_ptr, WARNING_PREFIX)) {
+ /* get the warning */
+ p = line_ptr + strlen (WARNING_PREFIX);
+ error_type = 2;
+ }
+#endif
+
+ if (error_type != 0) {
+ /* get the error/warning code */
+ error_code = strtol (p, (char **) &p, 10);
+ if (error_code != 0) {
+ p = g_strstr_len (p, line_length, MESSAGE_DELIMITER);
+ if (p != NULL) {
+ gchar *msg;
+
+ /* FIXME: think another
+ * solution for getting the
+ * messages, since this has
+ * problems with i18n as it's
+ * very difficult to get
+ * translated strings from the
+ * perl script */
+ p += strlen (MESSAGE_DELIMITER);
+ if (next_line != NULL)
+ msg = g_strndup (p, next_line - p - 1);
+ else
+ msg = g_strdup (p);
+
+ if (message->len > 0)
+ g_string_append (message, "\n");
+ g_string_append (message, msg);
+ g_free (msg);
+ }
+ }
+ }
+
+ line_ptr = next_line;
+ }
+
+ if (message->len > 0) {
+ err = g_error_new (GBF_PROJECT_ERROR,
+ GBF_PROJECT_ERROR_GENERAL_FAILURE,
+ "%s", message->str);
+ }
+ g_string_free (message, TRUE);
+
+ return err;
+}
+
+
+/*
+ * Process spawning ------------------------
+ */
+
+static void
+shutdown_channel (GbfAmSpawnData *data, GbfAmChannel *channel)
+{
+ if (channel->channel) {
+ g_io_channel_shutdown (channel->channel, TRUE, NULL);
+ g_io_channel_unref (channel->channel);
+ channel->channel = NULL;
+ }
+ if (channel->tag != 0) {
+ GMainContext *context = NULL;
+ GSource *source;
+ if (data->main_loop != NULL)
+ context = g_main_loop_get_context (data->main_loop);
+ source = g_main_context_find_source_by_id (context, channel->tag);
+ if (source != NULL)
+ g_source_destroy (source);
+ channel->tag = 0;
+ }
+}
+
+static void
+spawn_shutdown (GbfAmSpawnData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ if (data->child_pid) {
+ DEBUG (g_message ("Killing child"));
+ kill (data->child_pid, SIGKILL);
+ data->child_pid = 0;
+ }
+
+ /* close channels and remove sources */
+ shutdown_channel (data, &data->input);
+ shutdown_channel (data, &data->output);
+ shutdown_channel (data, &data->error);
+ data->open_channels = 0;
+
+ if (data->output.buffer) {
+ /* shrink buffer and add terminator */
+ data->output.buffer = g_realloc (data->output.buffer,
+ ++data->output.length);
+ data->output.buffer [data->output.length - 1] = 0;
+ }
+
+ if (data->error.buffer) {
+ /* shrink buffer and add terminator */
+ data->error.buffer = g_realloc (data->error.buffer,
+ ++data->error.length);
+ data->error.buffer [data->error.length - 1] = 0;
+ }
+
+ if (data->main_loop)
+ g_main_loop_quit (data->main_loop);
+}
+
+static void
+spawn_data_destroy (GbfAmSpawnData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ spawn_shutdown (data);
+
+ if (data->input.buffer) {
+ /* input buffer is provided by the user, so it's not freed here */
+ data->input.buffer = NULL;
+ data->input.size = 0;
+ data->input.length = 0;
+ }
+ if (data->output.buffer) {
+ g_free (data->output.buffer);
+ data->output.buffer = NULL;
+ data->output.size = 0;
+ data->output.length = 0;
+ }
+ if (data->error.buffer) {
+ g_free (data->error.buffer);
+ data->error.buffer = NULL;
+ data->error.size = 0;
+ data->error.length = 0;
+ }
+ g_free (data);
+}
+
+static gboolean
+spawn_write_child (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+ GbfAmSpawnData *data = user_data;
+ gboolean retval = FALSE;
+
+ g_assert (data != NULL);
+ g_assert (data->input.channel == ioc);
+
+ if (condition & G_IO_OUT) {
+ gsize bytes_written = 0;
+ GIOStatus status;
+ GError *error = NULL;
+
+ /* try to write all data remaining */
+ status = g_io_channel_write_chars (ioc,
+ data->input.buffer + data->input.length,
+ data->input.size - data->input.length,
+ &bytes_written, &error);
+ data->input.length += bytes_written;
+
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ DEBUG (g_message ("wrote %" G_GSIZE_FORMAT " bytes", bytes_written));
+
+ if (data->input.length < data->input.size) {
+ /* don't remove the source */
+ retval = TRUE;
+ }
+ break;
+
+ default:
+ if (error) {
+ g_warning ("Error while writing to stdin: %s",
+ error->message);
+ g_error_free (error);
+ }
+ break;
+ }
+ }
+
+ if (!retval) {
+ /* finished writing or some error ocurred */
+ g_io_channel_shutdown (data->input.channel, TRUE, NULL);
+ g_io_channel_unref (data->input.channel);
+ data->input.channel = NULL;
+ /* returning false will remove the source */
+ data->input.tag = 0;
+
+ data->open_channels--;
+ if (data->open_channels == 0) {
+ /* need to signal the end of the operation */
+ if (data->main_loop)
+ /* executing synchronously */
+ g_main_loop_quit (data->main_loop);
+ /* FIXME: what to do in the async case? */
+ }
+ }
+
+ return retval;
+}
+
+static gboolean
+read_channel (GbfAmChannel *channel, GIOCondition condition, GbfAmSpawnData *data)
+{
+ gboolean retval = FALSE;
+
+ if (condition & (G_IO_IN | G_IO_PRI)) {
+ gsize bytes_read = 0;
+ GIOStatus status;
+ GError *error = NULL;
+
+ /* allocate buffer */
+ if (channel->buffer == NULL) {
+ channel->size = READ_BUFFER_SIZE;
+ channel->buffer = g_malloc (channel->size);
+ channel->length = 0;
+ }
+
+ status = g_io_channel_read_chars (channel->channel,
+ channel->buffer + channel->length,
+ channel->size - channel->length,
+ &bytes_read, &error);
+ channel->length += bytes_read;
+
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ /* grow buffer if necessary */
+ if (channel->size - channel->length < READ_BUFFER_DELTA) {
+ channel->size += READ_BUFFER_DELTA;
+ channel->buffer = g_realloc (channel->buffer,
+ channel->size);
+ }
+ retval = TRUE;
+ break;
+
+ case G_IO_STATUS_EOF:
+ /* will close the channel */
+ break;
+
+ default:
+ if (error) {
+ g_warning ("Error while reading stderr: %s",
+ error->message);
+ g_error_free (error);
+ }
+ break;
+ }
+ }
+
+ if (!retval) {
+ /* eof was reached or some error ocurred */
+ g_io_channel_shutdown (channel->channel, FALSE, NULL);
+ g_io_channel_unref (channel->channel);
+ channel->channel = NULL;
+ /* returning false will remove the source */
+ channel->tag = 0;
+
+ data->open_channels--;
+ if (data->open_channels == 0) {
+ /* need to signal the end of the operation */
+ if (data->main_loop)
+ /* executing synchronously */
+ g_main_loop_quit (data->main_loop);
+ /* FIXME: what to do in the async case? */
+ }
+ }
+
+ return retval;
+}
+
+static gboolean
+spawn_read_output (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+ GbfAmSpawnData *data = user_data;
+
+ /* some checks first */
+ g_assert (data != NULL);
+ g_assert (ioc == data->output.channel);
+
+ return read_channel (&data->output, condition, data);
+}
+
+static gboolean
+spawn_read_error (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+ GbfAmSpawnData *data = user_data;
+
+ /* some checks first */
+ g_assert (data != NULL);
+ g_assert (ioc == data->error.channel);
+
+ return read_channel (&data->error, condition, data);
+}
+
+static gboolean
+spawn_kill_child (GbfAmSpawnData *data)
+{
+ /* we can't wait longer */
+ DEBUG (g_message ("Timeout: sending SIGTERM to child process"));
+
+ kill (data->child_pid, SIGTERM);
+
+ if (data->main_loop)
+ g_main_loop_quit (data->main_loop);
+
+ return FALSE;
+}
+
+static guint
+context_io_add_watch (GMainContext *context,
+ GIOChannel *channel,
+ GIOCondition condition,
+ GSourceFunc func,
+ gpointer user_data)
+{
+ GSource *source;
+ guint id;
+
+ g_return_val_if_fail (channel != NULL, 0);
+
+ source = g_io_create_watch (channel, condition);
+ g_source_set_callback (source, func, user_data, NULL);
+ id = g_source_attach (source, context);
+ g_source_unref (source);
+
+ return id;
+}
+
+static GbfAmSpawnData *
+spawn_script (gchar **argv,
+ gint timeout,
+ gchar *input,
+ gint input_size,
+ GIOFunc input_cb,
+ GIOFunc output_cb,
+ GIOFunc error_cb)
+{
+ GbfAmSpawnData *data;
+ gint child_in, child_out, child_err;
+ GError *error = NULL;
+ gboolean async;
+
+ data = g_new0 (GbfAmSpawnData, 1);
+
+ /* we consider timeout < 0 to mean asynchronous request */
+ async = (timeout <= 0);
+
+ /* setup default callbacks */
+ if (input_cb == NULL) input_cb = spawn_write_child;
+ if (output_cb == NULL) output_cb = spawn_read_output;
+ if (error_cb == NULL) error_cb = spawn_read_error;
+
+ /* set input buffer */
+ if (input) {
+ data->input.buffer = input;
+ data->input.size = input_size;
+ data->input.length = 0; /* for input buffer length acts as an index */
+ }
+
+ DEBUG (g_message ("Spawning script"));
+
+ if (!g_spawn_async_with_pipes (NULL, /* working dir */
+ argv,
+ NULL, /* environment */
+ 0, /* flags */
+ NULL, NULL, /* child setup func */
+ &data->child_pid,
+ &child_in, &child_out, &child_err,
+ &error)) {
+ g_warning ("Unable to fork: %s", error->message);
+ g_error_free (error);
+ g_free (data);
+ return NULL;
+
+ } else {
+ GMainContext *context = NULL;
+
+ if (!async) {
+ /* we need a new context to do the i/o
+ * otherwise we could have re-entrancy
+ * problems since gtk events will be processed
+ * while we iterate the inner main loop */
+ context = g_main_context_new ();
+ data->main_loop = g_main_loop_new (context, FALSE);
+ }
+
+ fcntl (child_in, F_SETFL, O_NONBLOCK);
+ fcntl (child_out, F_SETFL, O_NONBLOCK);
+ fcntl (child_err, F_SETFL, O_NONBLOCK);
+
+ data->open_channels = 3;
+ if (input != NULL && input_size > 0) {
+ data->input.channel = g_io_channel_unix_new (child_in);
+ data->input.tag = context_io_add_watch (context,
+ data->input.channel,
+ G_IO_OUT | G_IO_ERR |
+ G_IO_HUP | G_IO_NVAL,
+ (GSourceFunc) input_cb, data);
+ } else {
+ /* we are not interested in stdin */
+ close (child_in);
+ data->open_channels--;
+ }
+
+ /* create watches for stdout and stderr */
+ data->output.channel = g_io_channel_unix_new (child_out);
+ data->output.tag = context_io_add_watch (context,
+ data->output.channel,
+ G_IO_ERR | G_IO_HUP |
+ G_IO_NVAL | G_IO_IN,
+ (GSourceFunc) output_cb, data);
+ data->error.channel = g_io_channel_unix_new (child_err);
+ data->error.tag = context_io_add_watch (context,
+ data->error.channel,
+ G_IO_ERR | G_IO_HUP |
+ G_IO_NVAL | G_IO_IN,
+ (GSourceFunc) error_cb, data);
+
+ if (!async) {
+ GSource *source;
+
+ /* add the timeout */
+ source = g_timeout_source_new (timeout);
+ g_source_set_callback (source, (GSourceFunc) spawn_kill_child, data, NULL);
+ g_source_attach (source, context);
+ g_source_unref (source);
+
+ g_main_loop_run (data->main_loop);
+
+ /* continue iterations until all channels have been closed */
+ while (data->open_channels > 0 && g_main_context_pending (context))
+ g_main_context_iteration (context, FALSE);
+
+ /* close channels and remove io watches */
+ if (data->open_channels == 0)
+ /* normal shutdown */
+ data->child_pid = 0;
+ spawn_shutdown (data);
+
+ /* destroy main loop & context */
+ g_main_loop_unref (data->main_loop);
+ data->main_loop = NULL;
+ g_main_context_unref (context);
+ }
+
+ return data;
+ }
+}
+
+/*
+ * Script execution control ----------------------------
+ */
+
+static gboolean
+project_reload (GbfAmProject *project, GError **err)
+{
+ GbfAmSpawnData *data;
+ gchar *argv [5], *project_path;
+ gboolean retval;
+ gint i;
+
+ project_path = uri_to_path (project->project_root_uri);
+
+ i = 0;
+ argv [i++] = GBF_AM_PARSE;
+ DEBUG (argv [i++] = "-d");
+ argv [i++] = "--get";
+ argv [i++] = project_path;
+ argv [i++] = NULL;
+ g_assert (i <= G_N_ELEMENTS (argv));
+
+ data = spawn_script (argv, SCRIPT_TIMEOUT,
+ NULL, 0, /* input buffer */
+ NULL, NULL, NULL); /* i/o callbacks */
+
+ g_free (project_path);
+
+ retval = FALSE;
+ if (data != NULL) {
+ if (data->error.length > 0 && err != NULL) {
+ /* the buffer is zero terminated */
+ *err = parse_errors (project, data->error.buffer);
+ }
+
+ if (data->output.length > 0) {
+ gchar *xml_error = NULL;
+ retval = parse_output_xml (project,
+ data->output.buffer,
+ data->output.length,
+ NULL, &xml_error);
+ if (err != NULL && *err == NULL &&
+ !retval && xml_error) {
+ /* xml parse error */
+ g_set_error (err,
+ GBF_PROJECT_ERROR,
+ GBF_PROJECT_ERROR_GENERAL_FAILURE,
+ "XML parse error: %s",
+ xml_error);
+ }
+ g_free (xml_error);
+ } else {
+ /* FIXME: generate some kind of error here */
+ g_warning ("Child process returned no data");
+ }
+
+ spawn_data_destroy (data);
+ }
+
+ monitors_setup (project);
+
+ return retval;
+}
+
+static gboolean
+project_update (GbfAmProject *project,
+ xmlDocPtr doc,
+ GSList **change_set,
+ GError **err)
+{
+ GbfAmSpawnData *data;
+ gchar *argv [5];
+ gboolean retval;
+ gint i;
+ xmlChar *xml_doc;
+ gint xml_size;
+
+ /* remove file monitors */
+ monitors_remove (project);
+
+ i = 0;
+ argv [i++] = GBF_AM_PARSE;
+ DEBUG (argv [i++] = "-d");
+ argv [i++] = "--set";
+ argv [i++] = "-";
+ argv [i++] = NULL;
+ g_assert (i <= G_N_ELEMENTS (argv));
+
+ /* dump the document to memory */
+ xmlSubstituteEntitiesDefault (TRUE);
+ xmlDocDumpMemory (doc, &xml_doc, &xml_size);
+
+ /* execute the script */
+ data = spawn_script (argv, SCRIPT_TIMEOUT,
+ (char *) xml_doc, xml_size, /* input buffer */
+ NULL, NULL, NULL); /* i/o callbacks */
+ xmlFree (xml_doc);
+
+ retval = FALSE;
+ if (data != NULL) {
+ if (data->error.length > 0 && err != NULL) {
+ /* the buffer is zero terminated */
+ *err = parse_errors (project, data->error.buffer);
+ }
+
+ if (data->output.length > 0) {
+ gchar *xml_error = NULL;
+ /* process the xml output for changed groups */
+ retval = parse_output_xml (project,
+ data->output.buffer,
+ data->output.length,
+ change_set, &xml_error);
+ if (err != NULL && *err == NULL &&
+ !retval && xml_error) {
+ /* xml parse error */
+ g_set_error (err,
+ GBF_PROJECT_ERROR,
+ GBF_PROJECT_ERROR_GENERAL_FAILURE,
+ "XML parse error: %s",
+ xml_error);
+ }
+ g_free (xml_error);
+
+ /* FIXME: emit this only if the project has indeed changed */
+ g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+ }
+
+ spawn_data_destroy (data);
+ }
+
+ monitors_setup (project);
+
+ return retval;
+}
+
+/*
+ * ---------------- Data structures managment
+ */
+
+static void
+gbf_am_node_free (GbfAmNode *node)
+{
+ if (node) {
+ g_free (node->id);
+ g_free (node->name);
+ g_free (node->detail);
+ g_free (node->uri);
+ gbf_am_config_mapping_destroy (node->config);
+
+ g_free (node);
+ }
+}
+
+static GNode *
+project_node_new (GbfAmNodeType type)
+{
+ GbfAmNode *node;
+
+ node = g_new0 (GbfAmNode, 1);
+ node->type = type;
+
+ return g_node_new (node);
+}
+
+static gboolean
+foreach_node_destroy (GNode *g_node,
+ gpointer data)
+{
+ GbfAmProject *project = data;
+
+ switch (GBF_AM_NODE (g_node)->type) {
+ case GBF_AM_NODE_GROUP:
+ g_hash_table_remove (project->groups, GBF_AM_NODE (g_node)->id);
+ break;
+ case GBF_AM_NODE_TARGET:
+ g_hash_table_remove (project->targets, GBF_AM_NODE (g_node)->id);
+ break;
+ case GBF_AM_NODE_SOURCE:
+ g_hash_table_remove (project->sources, GBF_AM_NODE (g_node)->id);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ gbf_am_node_free (GBF_AM_NODE (g_node));
+
+ return FALSE;
+}
+
+static void
+project_node_destroy (GbfAmProject *project, GNode *g_node)
+{
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (GBF_IS_AM_PROJECT (project));
+
+ if (g_node) {
+ /* free each node's data first */
+ g_node_traverse (g_node,
+ G_IN_ORDER, G_TRAVERSE_ALL, -1,
+ foreach_node_destroy, project);
+
+ /* now destroy the tree itself */
+ g_node_destroy (g_node);
+ }
+}
+
+static void
+project_data_destroy (GbfAmProject *project)
+{
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (GBF_IS_AM_PROJECT (project));
+
+ monitors_remove (project);
+
+ /* project data */
+ project_node_destroy (project, project->root_node);
+ project->root_node = NULL;
+ g_free (project->project_file);
+ project->project_file = NULL;
+ gbf_am_config_mapping_destroy (project->project_config);
+ project->project_config = NULL;
+
+ /* shortcut hash tables */
+ if (project->groups) g_hash_table_destroy (project->groups);
+ if (project->targets) g_hash_table_destroy (project->targets);
+ if (project->sources) g_hash_table_destroy (project->sources);
+ project->groups = NULL;
+ project->targets = NULL;
+ project->sources = NULL;
+}
+
+static void
+project_data_init (GbfAmProject *project)
+{
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (GBF_IS_AM_PROJECT (project));
+
+ /* free data if necessary */
+ project_data_destroy (project);
+
+ /* FIXME: initialize monitors here, since monitors' lifecycle
+ * are bound to source files from the project */
+
+ /* project data */
+ project->project_file = NULL;
+ project->project_config = gbf_am_config_mapping_new ();
+ project->root_node = NULL;
+
+ /* shortcut hash tables */
+ project->groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ project->targets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ project->sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+}
+
+GbfAmConfigMapping *
+gbf_am_project_get_config (GbfAmProject *project, GError **error)
+{
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return gbf_am_config_mapping_copy (project->project_config);
+}
+
+GbfAmConfigMapping *
+gbf_am_project_get_group_config (GbfAmProject *project, const gchar *group_id,
+ GError **error)
+{
+ GbfAmNode *node;
+ GNode *g_node;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_node = g_hash_table_lookup (project->groups, group_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group doesn't exist"));
+ return NULL;
+ }
+ node = GBF_AM_NODE (g_node);
+ return gbf_am_config_mapping_copy (node->config);
+}
+
+GbfAmConfigMapping *
+gbf_am_project_get_target_config (GbfAmProject *project, const gchar *target_id,
+ GError **error)
+{
+ GbfAmNode *node;
+ GNode *g_node;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_node = g_hash_table_lookup (project->targets, target_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target doesn't exist"));
+ return NULL;
+ }
+ node = GBF_AM_NODE (g_node);
+ return gbf_am_config_mapping_copy (node->config);
+}
+
+void
+gbf_am_project_set_config (GbfAmProject *project,
+ GbfAmConfigMapping *new_config, GError **error)
+{
+ xmlDocPtr doc;
+ GSList *change_set = NULL;
+
+ g_return_if_fail (GBF_IS_AM_PROJECT (project));
+ g_return_if_fail (new_config != NULL);
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+
+ if (!xml_write_set_config (project, doc, NULL, new_config)) {
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/set-config.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return;
+ }
+ xmlFreeDoc (doc);
+ change_set_destroy (change_set);
+}
+
+void
+gbf_am_project_set_group_config (GbfAmProject *project, const gchar *group_id,
+ GbfAmConfigMapping *new_config, GError **error)
+{
+ GbfAmNode *node;
+ xmlDocPtr doc;
+ GNode *g_node;
+ GSList *change_set = NULL;
+
+ g_return_if_fail (GBF_IS_AM_PROJECT (project));
+ g_return_if_fail (new_config != NULL);
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ g_node = g_hash_table_lookup (project->groups, group_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group doesn't exist"));
+ return;
+ }
+ node = GBF_AM_NODE (g_node);
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_set_config (project, doc, g_node, new_config)) {
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/set-config.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return;
+ }
+ xmlFreeDoc (doc);
+ change_set_destroy (change_set);
+}
+
+void
+gbf_am_project_set_target_config (GbfAmProject *project,
+ const gchar *target_id,
+ GbfAmConfigMapping *new_config,
+ GError **error)
+{
+ xmlDocPtr doc;
+ GNode *g_node;
+ GSList *change_set = NULL;
+
+ g_return_if_fail (GBF_IS_AM_PROJECT (project));
+ g_return_if_fail (new_config != NULL);
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ g_node = g_hash_table_lookup (project->targets, target_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target doesn't exist"));
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_set_config (project, doc, g_node, new_config)) {
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/set-config.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return;
+ }
+ xmlFreeDoc (doc);
+ change_set_destroy (change_set);
+}
+
+/*
+ * GbfProjectIface methods ------------------------------------------
+ */
+
+static void
+impl_load (GbfProject *_project,
+ const gchar *uri,
+ GError **error)
+{
+ GbfAmProject *project;
+ gchar *root_path;
+
+ g_return_if_fail (GBF_IS_AM_PROJECT (_project));
+
+ project = GBF_AM_PROJECT (_project);
+ if (project->project_root_uri) {
+ /* FIXME:
+ * - do we really want to allow object reutilization
+ * - cancel some pending operations in the queue?
+ */
+ project_data_destroy (project);
+ g_free (project->project_root_uri);
+ project->project_root_uri = NULL;
+
+ project_data_init (project);
+ }
+
+ /* allow this? */
+ if (uri == NULL)
+ return;
+
+ /* check that the uri is in the filesystem */
+ project->project_root_uri = uri_normalize (uri, NULL);
+ if (project->project_root_uri == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Invalid or remote path (only local paths supported)"));
+ return;
+ }
+
+ /* some basic checks */
+ root_path = uri_to_path (project->project_root_uri);
+ if (root_path == NULL || !g_file_test (root_path, G_FILE_TEST_IS_DIR)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Project doesn't exist or invalid path"));
+ g_free (root_path);
+ g_free (project->project_root_uri);
+ project->project_root_uri = NULL;
+ return;
+ }
+ g_free (root_path);
+
+ /* now try loading the project */
+ if (!project_reload (project, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Malformed project"));
+ g_free (project->project_root_uri);
+ project->project_root_uri = NULL;
+ }
+}
+
+static gboolean
+file_exists (const gchar *path, const gchar *filename)
+{
+ gchar *full_path;
+ gboolean retval;
+
+ full_path = g_build_filename (path, filename, NULL);
+ retval = g_file_test (full_path, G_FILE_TEST_EXISTS);
+ g_free (full_path);
+
+ return retval;
+}
+
+static gboolean
+impl_probe (GbfProject *_project,
+ const gchar *path,
+ GError **error)
+{
+ gchar *normalized_uri, *root_path;
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), FALSE);
+
+ normalized_uri = uri_normalize (path, NULL);
+ if (normalized_uri != NULL) {
+ root_path = uri_to_path (normalized_uri);
+ if (root_path != NULL && g_file_test (root_path, G_FILE_TEST_IS_DIR)) {
+ retval = (file_exists (root_path, "Makefile.am") &&
+ (file_exists (root_path, "configure.in") ||
+ file_exists (root_path, "configure.ac")));
+ g_free (root_path);
+ }
+ g_free (normalized_uri);
+ }
+
+ return retval;
+}
+
+static void
+impl_refresh (GbfProject *_project,
+ GError **error)
+{
+ GbfAmProject *project;
+
+ g_return_if_fail (GBF_IS_AM_PROJECT (_project));
+
+ project = GBF_AM_PROJECT (_project);
+
+ if (project_reload (project, error))
+ g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+}
+
+static GbfProjectCapabilities
+impl_get_capabilities (GbfProject *_project, GError **error)
+{
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (_project),
+ GBF_PROJECT_CAN_ADD_NONE);
+ return (GBF_PROJECT_CAN_ADD_GROUP |
+ GBF_PROJECT_CAN_ADD_TARGET |
+ GBF_PROJECT_CAN_ADD_SOURCE);
+}
+
+static GbfProjectGroup *
+impl_get_group (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfAmProject *project;
+ GbfProjectGroup *group;
+ GNode *g_node;
+ GbfAmNode *node;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+ project = GBF_AM_PROJECT (_project);
+ g_node = g_hash_table_lookup (project->groups, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group doesn't exist"));
+ return NULL;
+ }
+ node = GBF_AM_NODE (g_node);
+
+ group = g_new0 (GbfProjectGroup, 1);
+ group->id = g_strdup (node->id);
+ group->name = g_strdup (node->name);
+ if (g_node->parent)
+ group->parent_id = g_strdup (GBF_AM_NODE (g_node->parent)->id);
+ else
+ group->parent_id = NULL;
+ group->groups = NULL;
+ group->targets = NULL;
+
+ /* add subgroups and targets of the group */
+ g_node = g_node_first_child (g_node);
+ while (g_node) {
+ node = GBF_AM_NODE (g_node);
+ switch (node->type) {
+ case GBF_AM_NODE_GROUP:
+ group->groups = g_list_prepend (group->groups,
+ g_strdup (node->id));
+ break;
+ case GBF_AM_NODE_TARGET:
+ group->targets = g_list_prepend (group->targets,
+ g_strdup (node->id));
+ break;
+ default:
+ break;
+ }
+ g_node = g_node_next_sibling (g_node);
+ }
+ group->groups = g_list_reverse (group->groups);
+ group->targets = g_list_reverse (group->targets);
+
+ return group;
+}
+
+static void
+foreach_group (gpointer key, gpointer value, gpointer data)
+{
+ GList **groups = data;
+
+ *groups = g_list_prepend (*groups, g_strdup (key));
+}
+
+
+static GList *
+impl_get_all_groups (GbfProject *_project,
+ GError **error)
+{
+ GbfAmProject *project;
+ GList *groups = NULL;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+ project = GBF_AM_PROJECT (_project);
+ g_hash_table_foreach (project->groups, foreach_group, &groups);
+
+ return groups;
+}
+
+static GtkWidget *
+impl_configure_new_group (GbfProject *_project,
+ GError **error)
+{
+ UNIMPLEMENTED;
+
+ return NULL;
+}
+
+static GtkWidget *
+impl_configure_group (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GtkWidget *wid = NULL;
+ GError *err = NULL;
+
+ g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ wid = gbf_am_properties_get_group_widget (GBF_AM_PROJECT (_project),
+ id, &err);
+ if (err) {
+ g_propagate_error (error, err);
+ }
+ return wid;
+}
+
+static gchar *
+impl_add_group (GbfProject *_project,
+ const gchar *parent_id,
+ const gchar *name,
+ GError **error)
+{
+ GbfAmProject *project;
+ GNode *g_node, *iter_node;
+ xmlDocPtr doc;
+ GSList *change_set = NULL;
+ GbfAmChange *change;
+ gchar *retval;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+ project = GBF_AM_PROJECT (_project);
+
+ /* Validate group name */
+ if (!name || strlen (name) <= 0)
+ {
+ error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+ _("Please specify group name"));
+ return NULL;
+ }
+ {
+ gboolean failed = FALSE;
+ const gchar *ptr = name;
+ while (*ptr) {
+ if (!isalnum (*ptr) && *ptr != '.' && *ptr != '-' &&
+ *ptr != '_')
+ failed = TRUE;
+ ptr++;
+ }
+ if (failed) {
+ error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+ _("Group name can only contain alphanumeric, '_', '-' or '.' characters"));
+ return NULL;
+ }
+ }
+
+ /* find the parent group */
+ g_node = g_hash_table_lookup (project->groups, parent_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Parent group doesn't exist"));
+ return NULL;
+ }
+
+ /* check that the new group doesn't already exist */
+ iter_node = g_node_first_child (g_node);
+ while (iter_node) {
+ GbfAmNode *node = GBF_AM_NODE (iter_node);
+ if (node->type == GBF_AM_NODE_GROUP &&
+ !strcmp (node->name, name)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group already exists"));
+ return NULL;
+ }
+ iter_node = g_node_next_sibling (iter_node);
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_add_group (project, doc, g_node, name)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group couldn't be created"));
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/add-group.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+ xmlFreeDoc (doc);
+
+ /* get newly created group id */
+ retval = NULL;
+ DEBUG (change_set_debug_print (change_set));
+ change = change_set_find (change_set, GBF_AM_CHANGE_ADDED, GBF_AM_NODE_GROUP);
+ if (change) {
+ retval = g_strdup (change->id);
+ } else {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group couldn't be created"));
+ }
+ change_set_destroy (change_set);
+
+ return retval;
+}
+
+static void
+impl_remove_group (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfAmProject *project;
+ GNode *g_node;
+ xmlDocPtr doc;
+ GSList *change_set = NULL;
+
+ g_return_if_fail (GBF_IS_AM_PROJECT (_project));
+
+ project = GBF_AM_PROJECT (_project);
+
+ /* Find the target */
+ g_node = g_hash_table_lookup (project->groups, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group doesn't exist"));
+ return;
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_remove_group (project, doc, g_node)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group couldn't be removed"));
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/remove-group.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ }
+ xmlFreeDoc (doc);
+ change_set_destroy (change_set);
+}
+
+static GbfProjectTarget *
+impl_get_target (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfAmProject *project;
+ GbfProjectTarget *target;
+ GNode *g_node;
+ GbfAmNode *node;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+ project = GBF_AM_PROJECT (_project);
+ g_node = g_hash_table_lookup (project->targets, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target doesn't exist"));
+ return NULL;
+ }
+ node = GBF_AM_NODE (g_node);
+
+ target = g_new0 (GbfProjectTarget, 1);
+ target->id = g_strdup (node->id);
+ target->name = g_strdup (node->name);
+ target->type = g_strdup (node->detail);
+ target->group_id = g_strdup (GBF_AM_NODE (g_node->parent)->id);
+ target->sources = NULL;
+
+ /* add sources to the target */
+ g_node = g_node_first_child (g_node);
+ while (g_node) {
+ node = GBF_AM_NODE (g_node);
+ switch (node->type) {
+ case GBF_AM_NODE_SOURCE:
+ target->sources = g_list_prepend (target->sources,
+ g_strdup (node->id));
+ break;
+ default:
+ break;
+ }
+ g_node = g_node_next_sibling (g_node);
+ }
+ target->sources = g_list_reverse (target->sources);
+
+ return target;
+}
+
+static void
+foreach_target (gpointer key, gpointer value, gpointer data)
+{
+ GList **targets = data;
+
+ *targets = g_list_prepend (*targets, g_strdup (key));
+}
+
+static GList *
+impl_get_all_targets (GbfProject *_project,
+ GError **error)
+{
+ GbfAmProject *project;
+ GList *targets = NULL;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+ project = GBF_AM_PROJECT (_project);
+ g_hash_table_foreach (project->targets, foreach_target, &targets);
+
+ return targets;
+}
+
+static GtkWidget *
+impl_configure_new_target (GbfProject *_project,
+ GError **error)
+{
+ UNIMPLEMENTED;
+
+ return NULL;
+}
+
+static GtkWidget *
+impl_configure_target (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GtkWidget *wid = NULL;
+ GError *err = NULL;
+
+ g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ wid = gbf_am_properties_get_target_widget (GBF_AM_PROJECT (_project),
+ id, &err);
+ if (err) {
+ g_propagate_error (error, err);
+ }
+ return wid;
+}
+
+static char *
+impl_add_target (GbfProject *_project,
+ const gchar *group_id,
+ const gchar *name,
+ const gchar *type,
+ GError **error)
+{
+ GbfAmProject *project;
+ GNode *g_node, *iter_node;
+ xmlDocPtr doc;
+ GSList *change_set = NULL;
+ GbfAmChange *change;
+ gchar *retval;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+ g_return_val_if_fail (type != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ project = GBF_AM_PROJECT (_project);
+
+ /* find the group */
+ g_node = g_hash_table_lookup (project->groups, group_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group doesn't exist"));
+ return NULL;
+ }
+
+ /* Validate target name */
+ if (!name || strlen (name) <= 0)
+ {
+ error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+ _("Please specify target name"));
+ return NULL;
+ }
+ {
+ gboolean failed = FALSE;
+ const gchar *ptr = name;
+ while (*ptr) {
+ if (!isalnum (*ptr) && *ptr != '.' && *ptr != '-' &&
+ *ptr != '_')
+ failed = TRUE;
+ ptr++;
+ }
+ if (failed) {
+ error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+ _("Target name can only contain alphanumeric, '_', '-' or '.' characters"));
+ return NULL;
+ }
+ }
+ if (!strcmp (type, "shared_lib")) {
+ if (strlen (name) < 7 ||
+ strncmp (name, "lib", strlen("lib")) != 0 ||
+ strcmp (&name[strlen(name) - 3], ".la") != 0) {
+ error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+ _("Shared library target name must be of the form 'libxxx.la'"));
+ return NULL;
+ }
+ }
+ else if (!strcmp (type, "static_lib")) {
+ if (strlen (name) < 6 ||
+ strncmp (name, "lib", strlen("lib")) != 0 ||
+ strcmp (&name[strlen(name) - 2], ".a") != 0) {
+ error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+ _("Static library target name must be of the form 'libxxx.a'"));
+ return NULL;
+ }
+ }
+
+ /* check that the target doesn't already exist */
+ iter_node = g_node_first_child (g_node);
+ while (iter_node) {
+ GbfAmNode *node = GBF_AM_NODE (iter_node);
+ if (node->type == GBF_AM_NODE_TARGET &&
+ !strcmp (node->name, name)) {
+ error_set (error, GBF_PROJECT_ERROR_ALREADY_EXISTS,
+ _("Target already exists"));
+ return NULL;
+ }
+ iter_node = g_node_next_sibling (iter_node);
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_add_target (project, doc, g_node, name, type)) {
+ error_set (error, GBF_PROJECT_ERROR_GENERAL_FAILURE,
+ _("General failure in target creation"));
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/add-target.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+ xmlFreeDoc (doc);
+
+ /* get newly created target id */
+ retval = NULL;
+ DEBUG (change_set_debug_print (change_set));
+ change = change_set_find (change_set, GBF_AM_CHANGE_ADDED,
+ GBF_AM_NODE_TARGET);
+ if (change) {
+ retval = g_strdup (change->id);
+ } else {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Newly created target could not be identified"));
+ }
+ change_set_destroy (change_set);
+
+ return retval;
+}
+
+static void
+impl_remove_target (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfAmProject *project;
+ GNode *g_node;
+ xmlDocPtr doc;
+ GSList *change_set = NULL;
+
+ g_return_if_fail (GBF_IS_AM_PROJECT (_project));
+
+ project = GBF_AM_PROJECT (_project);
+
+ /* Find the target */
+ g_node = g_hash_table_lookup (project->targets, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target doesn't exist"));
+ return;
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_remove_target (project, doc, g_node)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target couldn't be removed"));
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/remove-target.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ }
+ xmlFreeDoc (doc);
+ change_set_destroy (change_set);
+}
+
+static const gchar *
+impl_name_for_type (GbfProject *_project,
+ const gchar *type)
+{
+ if (!strcmp (type, "static_lib")) {
+ return _("Static Library");
+ } else if (!strcmp (type, "shared_lib")) {
+ return _("Shared Library");
+ } else if (!strcmp (type, "headers")) {
+ return _("Header Files");
+ } else if (!strcmp (type, "man")) {
+ return _("Man Documentation");
+ } else if (!strcmp (type, "data")) {
+ return _("Miscellaneous Data");
+ } else if (!strcmp (type, "program")) {
+ return _("Program");
+ } else if (!strcmp (type, "script")) {
+ return _("Script");
+ } else if (!strcmp (type, "info")) {
+ return _("Info Documentation");
+ } else if (!strcmp (type, "java")) {
+ return _("Java Module");
+ } else if (!strcmp (type, "python")) {
+ return _("Python Module");
+ } else {
+ return _("Unknown");
+ }
+}
+
+static const gchar *
+impl_mimetype_for_type (GbfProject *_project,
+ const gchar *type)
+{
+ if (!strcmp (type, "static_lib")) {
+ return "application/x-archive";
+ } else if (!strcmp (type, "shared_lib")) {
+ return "application/x-sharedlib";
+ } else if (!strcmp (type, "headers")) {
+ return "text/x-chdr";
+ } else if (!strcmp (type, "man")) {
+ return "text/x-troff-man";
+ } else if (!strcmp (type, "data")) {
+ return "application/octet-stream";
+ } else if (!strcmp (type, "program")) {
+ return "application/x-executable";
+ } else if (!strcmp (type, "script")) {
+ return "text/x-shellscript";
+ } else if (!strcmp (type, "info")) {
+ return "application/x-tex-info";
+ } else if (!strcmp (type, "java")) {
+ return "application/x-java";
+ } else if (!strcmp (type, "python")) {
+ return "application/x-python";
+ } else {
+ return "text/plain";
+ }
+}
+
+static gchar **
+impl_get_types (GbfProject *_project)
+{
+ return g_strsplit ("program:shared_lib:static_lib:headers:"
+ "man:data:script:info:java:python", ":", 0);
+}
+
+static GbfProjectTargetSource *
+impl_get_source (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfAmProject *project;
+ GbfProjectTargetSource *source;
+ GNode *g_node;
+ GbfAmNode *node;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+ project = GBF_AM_PROJECT (_project);
+ g_node = g_hash_table_lookup (project->sources, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Source doesn't exist"));
+ return NULL;
+ }
+ node = GBF_AM_NODE (g_node);
+
+ source = g_new0 (GbfProjectTargetSource, 1);
+ source->id = g_strdup (node->id);
+ source->source_uri = g_strdup (node->uri);
+ source->target_id = g_strdup (GBF_AM_NODE (g_node->parent)->id);
+
+ return source;
+}
+
+static void
+foreach_source (gpointer key, gpointer value, gpointer data)
+{
+ GList **sources = data;
+
+ *sources = g_list_prepend (*sources, g_strdup (key));
+}
+
+static GList *
+impl_get_all_sources (GbfProject *_project,
+ GError **error)
+{
+ GbfAmProject *project;
+ GList *sources = NULL;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+
+ project = GBF_AM_PROJECT (_project);
+ g_hash_table_foreach (project->sources, foreach_source, &sources);
+
+ return sources;
+}
+
+static GtkWidget *
+impl_configure_new_source (GbfProject *_project,
+ GError **error)
+{
+ UNIMPLEMENTED;
+
+ return NULL;
+}
+
+static GtkWidget *
+impl_configure_source (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ UNIMPLEMENTED;
+
+ return NULL;
+}
+
+/**
+ * impl_add_source:
+ * @project:
+ * @target_id: the target ID to where to add the source
+ * @uri: an uri to the file, which can be absolute or relative to the target's group
+ * @error:
+ *
+ * Add source implementation. The uri must have the project root as its parent.
+ *
+ * Return value:
+ **/
+static gchar *
+impl_add_source (GbfProject *_project,
+ const gchar *target_id,
+ const gchar *uri,
+ GError **error)
+{
+ GbfAmProject *project;
+ GNode *g_node, *iter_node;
+ xmlDocPtr doc;
+ gboolean abort_action = FALSE;
+ gchar *full_uri = NULL;
+ gchar *group_uri;
+ GSList *change_set = NULL;
+ GbfAmChange *change;
+ gchar *retval;
+ gchar *filename;
+ const gchar *ptr;
+ gboolean failed = FALSE;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (_project), NULL);
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (target_id != NULL, NULL);
+
+ project = GBF_AM_PROJECT (_project);
+
+ filename = g_path_get_basename (uri);
+
+ /* Validate target name */
+ ptr = filename;
+ while (*ptr) {
+ if (!isalnum (*ptr) && *ptr != '.' && *ptr != '-' &&
+ *ptr != '_')
+ failed = TRUE;
+ ptr++;
+ }
+ if (failed) {
+ error_set (error, GBF_PROJECT_ERROR_VALIDATION_FAILED,
+ _("Source file name can only contain alphanumeric, '_', '-' or '.' characters"));
+ g_free (filename);
+ return NULL;
+ }
+
+ /* check target */
+ g_node = g_hash_table_lookup (project->targets, target_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target doesn't exist"));
+ return NULL;
+ }
+
+ /* if the uri is relative, resolve it against the target's
+ * group directory; we need to compute the group's uri
+ * first */
+ group_uri = uri_normalize (g_path_skip_root (GBF_AM_NODE (g_node->parent)->id),
+ project->project_root_uri);
+
+ full_uri = uri_normalize (uri, group_uri);
+
+ /* Check that the source uri is inside the project root */
+ if (!uri_is_parent (project->project_root_uri, full_uri)) {
+ /* If not, copy it to target's group directory */
+ GnomeVFSURI *group_vuri, *dest_vuri, *src_vuri;
+ GnomeVFSResult res;
+
+ src_vuri = gnome_vfs_uri_new (uri);
+ group_vuri = gnome_vfs_uri_new (group_uri);
+ dest_vuri = gnome_vfs_uri_append_file_name (group_vuri,
+ filename);
+
+ res = gnome_vfs_xfer_uri (src_vuri, dest_vuri,
+ GNOME_VFS_XFER_DEFAULT,
+ GNOME_VFS_XFER_ERROR_MODE_ABORT,
+ GNOME_VFS_XFER_OVERWRITE_MODE_ABORT,
+ NULL, NULL);
+ if (res != GNOME_VFS_OK && res != GNOME_VFS_ERROR_FILE_EXISTS) {
+ GbfProjectError gbf_err;
+ gchar *error_str =
+ g_strdup_printf ("Failed to copy source file inside project: %s",
+ gnome_vfs_result_to_string (res));
+ switch (res) {
+ case GNOME_VFS_ERROR_NOT_FOUND:
+ gbf_err = GBF_PROJECT_ERROR_DOESNT_EXIST;
+ break;
+ default:
+ gbf_err = GBF_PROJECT_ERROR_GENERAL_FAILURE;
+ break;
+ }
+ error_set (error, gbf_err, error_str);
+ g_free (error_str);
+ abort_action = TRUE;
+ } else {
+ g_free (full_uri);
+ full_uri = gnome_vfs_uri_to_string (dest_vuri,
+ GNOME_VFS_URI_HIDE_NONE);
+ }
+ gnome_vfs_uri_unref (src_vuri);
+ gnome_vfs_uri_unref (group_vuri);
+ gnome_vfs_uri_unref (dest_vuri);
+ }
+
+ g_free (group_uri);
+ g_free (filename);
+
+ /* check for source duplicates */
+ iter_node = g_node_first_child (g_node);
+ while (!abort_action && iter_node) {
+ GbfAmNode *node = GBF_AM_NODE (iter_node);
+
+ if (node->type == GBF_AM_NODE_SOURCE &&
+ uri_is_equal (full_uri, node->uri)) {
+ error_set (error, GBF_PROJECT_ERROR_ALREADY_EXISTS,
+ _("Source file is already in given target"));
+ abort_action = TRUE;
+ }
+ iter_node = g_node_next_sibling (iter_node);
+ }
+
+ /* have there been any errors? */
+ if (abort_action) {
+ g_free (full_uri);
+ return NULL;
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+
+ if (!xml_write_add_source (project, doc, g_node, full_uri)) {
+ error_set (error, GBF_PROJECT_ERROR_GENERAL_FAILURE,
+ _("General failure in adding source file"));
+ abort_action = TRUE;
+ }
+
+ g_free (full_uri);
+ if (abort_action) {
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/add-source.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+ xmlFreeDoc (doc);
+
+ /* get newly created source id */
+ retval = NULL;
+ DEBUG (change_set_debug_print (change_set));
+ change = change_set_find (change_set, GBF_AM_CHANGE_ADDED,
+ GBF_AM_NODE_SOURCE);
+ if (change) {
+ retval = g_strdup (change->id);
+ } else {
+ error_set (error, GBF_PROJECT_ERROR_GENERAL_FAILURE,
+ _("Newly added source file could not be identified"));
+ }
+ change_set_destroy (change_set);
+ return retval;
+}
+
+static void
+impl_remove_source (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfAmProject *project;
+ GNode *g_node;
+ xmlDocPtr doc;
+
+ g_return_if_fail (GBF_IS_AM_PROJECT (_project));
+
+ project = GBF_AM_PROJECT (_project);
+
+ /* Find the source */
+ g_node = g_hash_table_lookup (project->sources, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Source doesn't exist"));
+ return;
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_remove_source (project, doc, g_node)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Source couldn't be removed"));
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/remove-source.xml", doc);
+ });
+
+ /* Update the project */
+ /* FIXME: should get and process the change set to verify that
+ * the source has been removed? */
+ if (!project_update (project, doc, NULL, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ }
+ xmlFreeDoc (doc);
+}
+
+static GtkWidget *
+impl_configure (GbfProject *_project, GError **error)
+{
+ GtkWidget *wid = NULL;
+ GError *err = NULL;
+
+ g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ wid = gbf_am_properties_get_widget (GBF_AM_PROJECT (_project), &err);
+ if (err) {
+ g_propagate_error (error, err);
+ }
+ return wid;
+}
+
+static GList *
+impl_get_config_modules (GbfProject *project, GError **error)
+{
+ GbfAmConfigMapping *config;
+ GbfAmConfigValue *value;
+ GError* err = NULL;
+ const gchar* pkg_modules;
+ GList* result = NULL;
+
+ config = gbf_am_project_get_config (GBF_AM_PROJECT(project), &err);
+ if (err)
+ {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+ if ((value = gbf_am_config_mapping_lookup (config, "pkg_check_modules")) &&
+ (pkg_modules = gbf_am_config_value_get_string (value)))
+ {
+ gchar **module;
+ gchar **modules = g_strsplit (pkg_modules, ", ", -1);
+ for (module = modules; *module != NULL; ++module)
+ {
+ result = g_list_prepend (result, g_strdup (*module));
+ }
+ g_strfreev (modules);
+ }
+ return result;
+}
+
+static gboolean
+package_is_valid (const gchar* package)
+{
+ const gchar* c = package;
+ while (*c != '\0')
+ {
+ if (!g_ascii_isalnum (*c) &&
+ (*c != '_') && (*c != '-') && (*c != '.') && (*c != '+'))
+ {
+ return FALSE;
+ }
+ c++;
+ }
+ return TRUE;
+}
+
+static GList *
+impl_get_config_packages (GbfProject *project,
+ const gchar* module,
+ GError **error)
+{
+ GbfAmConfigMapping *config;
+ GbfAmConfigValue *module_info;
+ GbfAmConfigMapping *module_info_map;
+ GError* err = NULL;
+ GList* result = NULL;
+
+ config = gbf_am_project_get_config (GBF_AM_PROJECT(project), &err);
+ if (err)
+ {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+
+ gchar *module_key = g_strconcat ("pkg_check_modules_",
+ module,
+ NULL);
+
+ if ((module_info = gbf_am_config_mapping_lookup (config, module_key)) &&
+ (module_info_map = gbf_am_config_value_get_mapping (module_info)))
+ {
+ GbfAmConfigValue *pkgs_val;
+ const gchar *packages;
+
+ if ((pkgs_val = gbf_am_config_mapping_lookup (module_info_map, "packages")) &&
+ (packages = gbf_am_config_value_get_string (pkgs_val)))
+ {
+ gchar **pkgs, **pkg;
+ pkgs = g_strsplit (packages, ", ", -1);
+ for (pkg = pkgs; *pkg != NULL; ++pkg)
+ {
+ gchar* version;
+ if ((version = strchr (*pkg, ' ')))
+ *version = '\0';
+ if (package_is_valid (*pkg))
+ {
+ result = g_list_append (result,
+ g_strdup (*pkg));
+ }
+ }
+ g_strfreev (pkgs);
+ }
+ }
+ g_free (module_key);
+ return result;
+}
+
+static void
+gbf_am_project_class_init (GbfAmProjectClass *klass)
+{
+ GObjectClass *object_class;
+ GbfProjectClass *project_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ project_class = GBF_PROJECT_CLASS (klass);
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->dispose = gbf_am_project_dispose;
+ object_class->get_property = gbf_am_project_get_property;
+
+ project_class->load = impl_load;
+ project_class->probe = impl_probe;
+ project_class->refresh = impl_refresh;
+ project_class->get_capabilities = impl_get_capabilities;
+
+ project_class->add_group = impl_add_group;
+ project_class->remove_group = impl_remove_group;
+ project_class->get_group = impl_get_group;
+ project_class->get_all_groups = impl_get_all_groups;
+ project_class->configure_group = impl_configure_group;
+ project_class->configure_new_group = impl_configure_new_group;
+
+ project_class->add_target = impl_add_target;
+ project_class->remove_target = impl_remove_target;
+ project_class->get_target = impl_get_target;
+ project_class->get_all_targets = impl_get_all_targets;
+ project_class->configure_target = impl_configure_target;
+ project_class->configure_new_target = impl_configure_new_target;
+
+ project_class->add_source = impl_add_source;
+ project_class->remove_source = impl_remove_source;
+ project_class->get_source = impl_get_source;
+ project_class->get_all_sources = impl_get_all_sources;
+ project_class->configure_source = impl_configure_source;
+ project_class->configure_new_source = impl_configure_new_source;
+
+ project_class->configure = impl_configure;
+ project_class->get_config_modules = impl_get_config_modules;
+ project_class->get_config_packages = impl_get_config_packages;
+
+ project_class->name_for_type = impl_name_for_type;
+ project_class->mimetype_for_type = impl_mimetype_for_type;
+ project_class->get_types = impl_get_types;
+
+ /* default signal handlers */
+ project_class->project_updated = NULL;
+
+ /* FIXME: shouldn't we use '_' instead of '-' ? */
+ g_object_class_install_property
+ (object_class, PROP_PROJECT_DIR,
+ g_param_spec_string ("project-dir",
+ _("Project directory"),
+ _("Project directory"),
+ NULL,
+ G_PARAM_READABLE));
+}
+
+static void
+gbf_am_project_instance_init (GbfAmProject *project)
+{
+ /* initialize data & monitors */
+ project->project_root_uri = NULL;
+ project_data_init (project);
+
+ /* setup queue */
+ project->queue_ops = g_queue_new ();
+ project->queue_handler_tag = 0;
+
+ /* initialize build callbacks */
+ project->callbacks = NULL;
+
+ /* FIXME: those path should be configurable */
+ project->make_command = g_strdup ("/usr/bin/make");
+ project->configure_command = g_strdup ("./configure");
+ project->autogen_command = g_strdup ("./autogen.sh");
+ project->install_prefix = g_strdup ("/gnome");
+}
+
+static void
+gbf_am_project_dispose (GObject *object)
+{
+ GbfAmProject *project;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GBF_IS_AM_PROJECT (object));
+
+ project = GBF_AM_PROJECT (object);
+
+ /* project data & monitors */
+ project_data_destroy (project);
+ g_free (project->project_root_uri);
+ project->project_root_uri = NULL;
+
+ g_free (project->make_command);
+ g_free (project->configure_command);
+ g_free (project->autogen_command);
+ g_free (project->install_prefix);
+
+ GNOME_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
+}
+
+static void
+gbf_am_project_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbfAmProject *project = GBF_AM_PROJECT (object);
+
+ switch (prop_id) {
+ case PROP_PROJECT_DIR:
+ g_value_set_string (value, project->project_root_uri);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+GbfProject *
+gbf_am_project_new (void)
+{
+ return GBF_PROJECT (g_object_new (GBF_TYPE_AM_PROJECT, NULL));
+}
+
+GBF_BACKEND_BOILERPLATE (GbfAmProject, gbf_am_project);
Added: trunk/plugins/gbf-am/gbf-am-project.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-project.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,131 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-am-project.h
+ *
+ * Copyright (C) 2000 JP Rosevear
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: JP Rosevear
+ */
+
+#ifndef _GBF_AM_PROJECT_H_
+#define _GBF_AM_PROJECT_H_
+
+#include <glib-object.h>
+#include <libanjuta/gbf-project.h>
+#include <libanjuta/anjuta-plugin.h>
+#include "gbf-am-config.h"
+
+G_BEGIN_DECLS
+
+#define GBF_TYPE_AM_PROJECT (gbf_am_project_get_type (NULL))
+#define GBF_AM_PROJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GBF_TYPE_AM_PROJECT, GbfAmProject))
+#define GBF_AM_PROJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GBF_TYPE_AM_PROJECT, GbfAmProjectClass))
+#define GBF_IS_AM_PROJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GBF_TYPE_AM_PROJECT))
+#define GBF_IS_AM_PROJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GBF_TYPE_AM_PROJECT))
+
+typedef struct _GbfAmProject GbfAmProject;
+typedef struct _GbfAmProjectClass GbfAmProjectClass;
+typedef struct _GbfAmBuildCallback GbfAmBuildCallback;
+typedef struct _GbfAmNode GbfAmNode;
+
+typedef enum {
+ GBF_AM_NODE_GROUP,
+ GBF_AM_NODE_TARGET,
+ GBF_AM_NODE_SOURCE
+} GbfAmNodeType;
+
+struct _GbfAmNode {
+ GbfAmNodeType type;
+ gchar *id; /* unique id among nodes of the same type */
+ gchar *name; /* user visible string */
+ GbfAmConfigMapping *config;
+ gchar *uri; /* groups: path to Makefile.am;
+ targets: NULL;
+ sources: file uri */
+ gchar *detail; /* groups: NULL;
+ targets: target type;
+ sources: NULL or target dependency for built sources */
+};
+
+struct _GbfAmProject {
+ GbfProject parent;
+
+ /* uri of the project; this can be a full uri, even though we
+ * can only work with native local files */
+ gchar *project_root_uri;
+
+ /* project data */
+ gchar *project_file; /* configure.in uri */
+ GbfAmConfigMapping *project_config; /* project configuration
+ * (i.e. from configure.in) */
+ GNode *root_node; /* tree containing project data;
+ * each GNode's data is a
+ * GbfAmNode, and the root of
+ * the tree is the root group. */
+
+ /* shortcut hash tables, mapping id -> GNode from the tree above */
+ GHashTable *groups;
+ GHashTable *targets;
+ GHashTable *sources;
+
+ /* project files monitors */
+ GHashTable *monitors;
+
+ /* operations queue */
+ GQueue *queue_ops;
+ guint queue_handler_tag;
+
+ /* build callbacks */
+ GList *callbacks;
+
+ /* build config */
+ gchar *make_command;
+ gchar *configure_command;
+ gchar *autogen_command;
+ gchar *install_prefix;
+};
+
+struct _GbfAmProjectClass {
+ GbfProjectClass parent_class;
+};
+
+/* convenient shortcut macro the get the GbfAmNode from a GNode */
+#define GBF_AM_NODE(g_node) ((g_node) != NULL ? (GbfAmNode *)((g_node)->data) : NULL)
+
+GType gbf_am_project_get_type (GTypeModule *plugin);
+GbfProject *gbf_am_project_new (void);
+
+/* FIXME: The config infrastructure should probably be made part of GbfProject
+ * so that other backend implementations could use them directly and we don't
+ * have to create separate configuration widgets. But then different back end
+ * implementations could have significantly different config management
+ * warranting separate implementations.
+ */
+/* These functions returns a copy of the config. It should be free with
+ * gbf_am_config_value_free() when no longer required
+ */
+GbfAmConfigMapping *gbf_am_project_get_config (GbfAmProject *project, GError **error);
+GbfAmConfigMapping *gbf_am_project_get_group_config (GbfAmProject *project, const gchar *group_id, GError **error);
+GbfAmConfigMapping *gbf_am_project_get_target_config (GbfAmProject *project, const gchar *target_id, GError **error);
+
+void gbf_am_project_set_config (GbfAmProject *project, GbfAmConfigMapping *new_config, GError **error);
+void gbf_am_project_set_group_config (GbfAmProject *project, const gchar *group_id, GbfAmConfigMapping *new_config, GError **error);
+void gbf_am_project_set_target_config (GbfAmProject *project, const gchar *target_id, GbfAmConfigMapping *new_config, GError **error);
+
+G_END_DECLS
+
+#endif /* _GBF_AM_PROJECT_H_ */
Added: trunk/plugins/gbf-am/gbf-am-properties.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-properties.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,1577 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-am-properties.c
+ *
+ * Copyright (C) 2005 Naba Kumar
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Naba Kumar
+ */
+
+#include <config.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade-xml.h>
+
+#include <glib/gi18n.h>
+
+#include "gbf-am-config.h"
+#include "gbf-am-properties.h"
+
+#define GLADE_FILE PACKAGE_DATA_DIR "/glade/gbf-am-dialogs.glade"
+
+typedef enum {
+ GBF_AM_CONFIG_LABEL,
+ GBF_AM_CONFIG_ENTRY,
+ GBF_AM_CONFIG_TEXT,
+ GBF_AM_CONFIG_LIST,
+} GbfConfigPropertyType;
+
+enum {
+ COL_PACKAGE,
+ COL_VERSION,
+ N_COLUMNS
+};
+
+enum {
+ COL_PKG_PACKAGE,
+ COL_PKG_DESCRIPTION,
+ N_PKG_COLUMNS
+};
+
+enum {
+ COL_VAR_NAME,
+ COL_VAR_VALUE,
+ N_VAR_COLUMNS
+};
+
+static void
+on_property_entry_changed (GtkEntry *entry, GbfAmConfigValue *value)
+{
+ gbf_am_config_value_set_string (value, gtk_entry_get_text (entry));
+}
+
+static void
+add_configure_property (GbfAmProject *project, GbfAmConfigMapping *config,
+ GbfConfigPropertyType prop_type,
+ const gchar *display_name, const gchar *direct_value,
+ const gchar *config_key, GtkWidget *table,
+ gint position)
+{
+ GtkWidget *label;
+ const gchar *value;
+ GtkWidget *widget;
+ GbfAmConfigValue *config_value = NULL;
+
+ value = "";
+ if (direct_value)
+ {
+ value = direct_value;
+ } else {
+ config_value = gbf_am_config_mapping_lookup (config,
+ config_key);
+ if (!config_value) {
+ config_value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+ gbf_am_config_value_set_string (config_value, "");
+ gbf_am_config_mapping_insert (config, config_key,
+ config_value);
+ }
+ if (config_value && config_value->type == GBF_AM_TYPE_STRING) {
+ const gchar *val_str;
+ val_str = gbf_am_config_value_get_string (config_value);
+ if (val_str)
+ value = val_str;
+ }
+ }
+
+ label = gtk_label_new (display_name);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, -1);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, position, position+1,
+ GTK_FILL, GTK_FILL, 5, 3);
+ switch (prop_type) {
+ case GBF_AM_CONFIG_LABEL:
+ widget = gtk_label_new (value);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+ break;
+ case GBF_AM_CONFIG_ENTRY:
+ widget = gtk_entry_new ();
+ gtk_entry_set_text (GTK_ENTRY (widget), value);
+ if (config_value) {
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (on_property_entry_changed),
+ config_value);
+ }
+ break;
+ default:
+ g_warning ("Should not reach here");
+ widget = gtk_label_new (_("Unknown"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+ }
+ gtk_widget_show (widget);
+ gtk_table_attach (GTK_TABLE (table), widget, 1, 2, position, position+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+}
+
+static void
+recursive_config_foreach_cb (const gchar *key, GbfAmConfigValue *value,
+ gpointer user_data)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+ GtkWidget *widget;
+ gint position;
+
+ table = GTK_WIDGET (user_data);
+ position = g_list_length (GTK_TABLE (table)->children);
+
+ label = gtk_label_new (key);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, -1);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1,
+ position, position+1,
+ GTK_FILL, GTK_FILL, 5, 3);
+
+ if (value->type == GBF_AM_TYPE_STRING) {
+ widget = gtk_entry_new ();
+ gtk_entry_set_text (GTK_ENTRY (widget),
+ gbf_am_config_value_get_string (value));
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (on_property_entry_changed),
+ value);
+ } else if (value->type == GBF_AM_TYPE_LIST) {
+ /* FIXME: */
+ widget = gtk_label_new ("FIXME");
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+ } else if (value->type == GBF_AM_TYPE_MAPPING) {
+ /* FIXME: */
+ widget = gtk_label_new ("FIXME");
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+ } else {
+ g_warning ("Should not be here");
+ widget = gtk_label_new (_("Unknown"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+ }
+ gtk_widget_show (widget);
+ gtk_table_attach (GTK_TABLE (table), widget, 1, 2,
+ position, position+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+}
+
+static GtkListStore *
+packages_get_pkgconfig_list (void)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gchar line[1024];
+ gchar *tmpfile, *pkg_cmd;
+ FILE *pkg_fd;
+
+ store = gtk_list_store_new (N_PKG_COLUMNS, G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ /* Now get all the pkg-config info */
+ tmpfile = g_strdup_printf ("%s%cpkgmodules--%d", g_get_tmp_dir (),
+ G_DIR_SEPARATOR, getpid());
+ pkg_cmd = g_strconcat ("pkg-config --list-all 2>/dev/null | sort > ",
+ tmpfile, NULL);
+ if (system (pkg_cmd) == -1)
+ return store;
+ pkg_fd = fopen (tmpfile, "r");
+ if (!pkg_fd) {
+ g_warning ("Can not open %s for reading", tmpfile);
+ g_free (tmpfile);
+ return store;
+ }
+ while (fgets (line, 1024, pkg_fd)) {
+ gchar *name_end;
+ gchar *desc_start;
+ gchar *description;
+ gchar *name;
+
+ if (line[0] == '\0')
+ continue;
+
+ name_end = line;
+ while (!isspace(*name_end))
+ name_end++;
+ desc_start = name_end;
+ while (isspace(*desc_start))
+ desc_start++;
+
+ name = g_strndup (line, name_end-line);
+ description = g_strndup (desc_start, strlen (desc_start)-1);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COL_PKG_PACKAGE, name,
+ COL_PKG_DESCRIPTION, description,
+ -1);
+ }
+ fclose (pkg_fd);
+ unlink (tmpfile);
+ g_free (tmpfile);
+ return store;
+}
+
+static void
+save_packages_list (GbfAmProject *project, GbfAmConfigMapping *config,
+ GtkTreeModel *model, GtkTreeIter *parent)
+{
+ gchar *key_name;
+ gchar *module_name;
+ GbfAmConfigValue *value;
+ GtkTreeIter child;
+ gboolean has_children;
+ GString *packages_list = g_string_new (NULL);
+
+ gtk_tree_model_get (model, parent, COL_PACKAGE, &module_name, -1);
+ has_children = gtk_tree_model_iter_children (model, &child, parent);
+ if (has_children)
+ {
+ do
+ {
+ gchar *package, *version;
+ gtk_tree_model_get (model, &child, COL_PACKAGE,
+ &package, COL_VERSION,
+ &version, -1);
+ if (strlen (packages_list->str) > 0)
+ g_string_append (packages_list, ", ");
+ g_string_append (packages_list, package);
+ if (version)
+ {
+ g_string_append (packages_list, " ");
+ g_string_append (packages_list, version);
+ }
+ g_free (package);
+ g_free (version);
+ }
+ while (gtk_tree_model_iter_next (model, &child));
+ }
+
+ if (strlen (packages_list->str) > 0)
+ {
+ GbfAmConfigMapping *pkgmodule;
+ key_name = g_strconcat ("pkg_check_modules_",
+ module_name,
+ NULL);
+
+ value = gbf_am_config_mapping_lookup (config, key_name);
+ if (!value)
+ {
+ pkgmodule = gbf_am_config_mapping_new ();
+ value = gbf_am_config_value_new (GBF_AM_TYPE_MAPPING);
+ gbf_am_config_value_set_mapping (value, pkgmodule);
+ gbf_am_config_mapping_insert (config,
+ key_name,
+ value);
+ }
+ pkgmodule = gbf_am_config_value_get_mapping (value);
+ value = gbf_am_config_mapping_lookup (pkgmodule,
+ "packages");
+ if (!value)
+ {
+ value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+ gbf_am_config_value_set_string (value, packages_list->str);
+ gbf_am_config_mapping_insert (pkgmodule,
+ "packages",
+ value);
+ }
+ else
+ {
+ gbf_am_config_value_set_string (value, packages_list->str);
+ }
+ g_free (key_name);
+ }
+ g_free (module_name);
+}
+
+static void
+package_edited_cb (GtkCellRendererText *cellrenderertext, gchar *arg1,
+ gchar *arg2, GtkWidget *top_level)
+{
+ GtkTreeView *treeview;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter, parent;
+ GtkTreeModel *model;
+ GbfAmProject *project;
+ GbfAmConfigMapping *config;
+ gboolean has_parent;
+
+ if (strcmp (arg1, arg2) == 0)
+ return;
+ project = g_object_get_data (G_OBJECT (top_level), "__project");
+ config = g_object_get_data (G_OBJECT (top_level), "__config");
+ treeview = g_object_get_data (G_OBJECT (project),
+ "__packages_treeview");
+ selection = gtk_tree_view_get_selection (treeview);
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+
+ /* If the selected node is module name and the module has children,
+ * do not alow editing it
+ */
+ has_parent = gtk_tree_model_iter_parent (model, &parent, &iter);
+ if (!has_parent &&
+ gtk_tree_model_iter_n_children (model, &iter) > 0)
+ return;
+ if (strcmp (arg2, _("Enter new module")) == 0 ||
+ strcmp (arg2, "") == 0)
+ gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
+ else
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter, COL_PACKAGE,
+ arg2, -1);
+ if (has_parent)
+ save_packages_list (project, config, model, &parent);
+}
+
+static void
+package_version_edited_cb (GtkCellRendererText *cellrenderertext, gchar *arg1,
+ gchar *arg2, GtkWidget *top_level)
+{
+ GtkTreeView *treeview;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter, parent;
+ GtkTreeModel *model;
+ GbfAmProject *project;
+ GbfAmConfigMapping *config;
+
+ if (strcmp (arg1, arg2) == 0)
+ return;
+ project = g_object_get_data (G_OBJECT (top_level), "__project");
+ config = g_object_get_data (G_OBJECT (top_level), "__config");
+
+ treeview = g_object_get_data (G_OBJECT (project),
+ "__packages_treeview");
+ selection = gtk_tree_view_get_selection (treeview);
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+ if (!gtk_tree_model_iter_parent (model, &parent, &iter))
+ return;
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter, COL_VERSION,
+ arg2, -1);
+ save_packages_list (project, config, model, &parent);
+}
+
+static void
+add_package_module_clicked_cb (GtkWidget *button, GbfAmProject *project)
+{
+ GtkTreeView *treeview;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+
+ treeview = g_object_get_data (G_OBJECT (project),
+ "__packages_treeview");
+ model = gtk_tree_view_get_model (treeview);
+ gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter, COL_PACKAGE,
+ _("Enter new module"), -1);
+ selection = gtk_tree_view_get_selection (treeview);
+ gtk_tree_selection_select_iter (selection, &iter);
+
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(treeview), path,
+ NULL, FALSE, 0, 0);
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW(treeview), path,
+ gtk_tree_view_get_column(GTK_TREE_VIEW(treeview),
+ COL_PACKAGE), TRUE);
+ gtk_tree_path_free (path);
+}
+
+static void
+add_package_clicked_cb (GtkWidget *button, GbfAmProject *project)
+{
+ GladeXML *gxml;
+ GtkTreeView *treeview;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter, parent;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkWidget *dlg;
+ GtkWidget *pkg_treeview;
+ GtkListStore *store;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *col;
+ gchar *pkg_to_add = NULL;
+ GbfAmConfigMapping *config;
+
+ /* Let user select a package */
+ gxml = glade_xml_new (GLADE_FILE, "package_selection_dialog",
+ GETTEXT_PACKAGE);
+ dlg = glade_xml_get_widget (gxml, "package_selection_dialog");
+ pkg_treeview = glade_xml_get_widget (gxml, "pkg_treeview");
+ renderer = gtk_cell_renderer_text_new ();
+ col = gtk_tree_view_column_new_with_attributes (_("Module/Packages"),
+ renderer,
+ "text", COL_PKG_PACKAGE,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (pkg_treeview), col);
+ renderer = gtk_cell_renderer_text_new ();
+ col = gtk_tree_view_column_new_with_attributes (_("Version"),
+ renderer,
+ "text",
+ COL_PKG_DESCRIPTION,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (pkg_treeview), col);
+
+ store = packages_get_pkgconfig_list ();
+ gtk_tree_view_set_model (GTK_TREE_VIEW (pkg_treeview),
+ GTK_TREE_MODEL (store));
+ if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_ACCEPT)
+ {
+ GtkTreeSelection *sel;
+ GtkTreeIter pkg_iter;
+
+ sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (pkg_treeview));
+ if (gtk_tree_selection_get_selected (sel, NULL, &pkg_iter))
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (store),
+ &pkg_iter, COL_PKG_PACKAGE,
+ &pkg_to_add, -1);
+ }
+ }
+ gtk_widget_destroy (dlg);
+
+ if (!pkg_to_add)
+ return;
+
+ treeview = g_object_get_data (G_OBJECT (project),
+ "__packages_treeview");
+ config = g_object_get_data (G_OBJECT (project), "__config");
+
+ selection = gtk_tree_view_get_selection (treeview);
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ g_free (pkg_to_add);
+ return;
+ }
+ if (!gtk_tree_model_iter_parent (model, &parent, &iter))
+ gtk_tree_selection_get_selected (selection, &model, &parent);
+ gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent);
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ COL_PACKAGE, pkg_to_add, -1);
+ save_packages_list (project, config, model, &parent);
+
+ g_free (pkg_to_add);
+
+ path = gtk_tree_model_get_path (model, &parent);
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (treeview), path, TRUE);
+ gtk_tree_path_free (path);
+
+ gtk_tree_selection_select_iter (selection, &iter);
+
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(treeview), path,
+ NULL, FALSE, 0, 0);
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW(treeview), path,
+ gtk_tree_view_get_column(GTK_TREE_VIEW(treeview),
+ COL_PACKAGE),
+ FALSE);
+ gtk_tree_path_free (path);
+}
+
+static void
+remove_package_clicked_cb (GtkWidget *button, GbfAmProject *project)
+{
+ GtkTreeView *treeview;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter, parent;
+ GtkTreeModel *model;
+ const gchar *msg;
+ gchar *name;
+ GtkWidget *dlg;
+ gboolean has_parent;
+ GbfAmConfigMapping *config;
+
+ treeview = g_object_get_data (G_OBJECT (project),
+ "__packages_treeview");
+ config = g_object_get_data (G_OBJECT (project),
+ "__config");
+ selection = gtk_tree_view_get_selection (treeview);
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+ gtk_tree_model_get (model, &iter, COL_PACKAGE, &name, -1);
+ has_parent = gtk_tree_model_iter_parent (model, &parent, &iter);
+ if (!has_parent)
+ msg = _("Are you sure you want to remove module \"%s\" and all its associated packages?");
+ else
+ msg = _("Are you sure you want to remove package \"%s\"?");
+ dlg = gtk_message_dialog_new_with_markup (NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_YES_NO,
+ msg, name);
+ if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_YES)
+ gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
+ gtk_widget_destroy (dlg);
+ g_free (name);
+ if (has_parent)
+ save_packages_list (project, config, model, &parent);
+}
+
+static void
+packages_treeview_selection_changed_cb (GtkTreeSelection *selection,
+ GbfAmProject *project)
+{
+ GtkTreeIter iter/*, parent*/;
+ GtkTreeModel *model;
+ GtkWidget *add_module_button, *add_package_button, *remove_button;
+
+ add_module_button = g_object_get_data (G_OBJECT (project),
+ "__add_module_button");
+ add_package_button = g_object_get_data (G_OBJECT (project),
+ "__add_package_button");
+ remove_button = g_object_get_data (G_OBJECT (project),
+ "__remove_button");
+
+ gtk_widget_set_sensitive (add_module_button, TRUE);
+ gtk_widget_set_sensitive (add_package_button, TRUE);
+ gtk_widget_set_sensitive (remove_button, TRUE);
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_widget_set_sensitive (add_package_button, FALSE);
+ gtk_widget_set_sensitive (remove_button, FALSE);
+ }
+}
+
+static void
+variable_name_edited_cb (GtkCellRendererText *cellrenderertext, gchar *arg1,
+ gchar *arg2, GtkWidget *top_level)
+{
+ GtkTreeView *treeview;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GbfAmProject *project;
+
+ project = g_object_get_data (G_OBJECT (top_level), "__project");
+ treeview = g_object_get_data (G_OBJECT (project),
+ "__variables_treeview");
+ selection = gtk_tree_view_get_selection (treeview);
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+ g_message ("Var name = %s", arg2);
+ if (strcmp (arg2, _("Enter new variable")) == 0 ||
+ strcmp (arg2, "") == 0)
+ {
+ gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+ }
+ else if (strcmp (arg1, arg2) != 0 && strlen (arg2) != 0)
+ {
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, COL_VAR_NAME,
+ arg2, -1);
+ }
+}
+
+static void
+variable_value_edited_cb (GtkCellRendererText *cellrenderertext, gchar *arg1,
+ gchar *arg2, GtkWidget *top_level)
+{
+ GtkTreeView *treeview;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GbfAmConfigMapping *config;
+ GbfAmConfigMapping *variables;
+ GbfAmConfigValue *value;
+ GbfAmProject *project;
+ gchar *variable_name;
+
+ project = g_object_get_data (G_OBJECT (top_level), "__project");
+ config = g_object_get_data (G_OBJECT (top_level), "__config");
+
+ if (strcmp (arg1, arg2) == 0)
+ return;
+ treeview = g_object_get_data (G_OBJECT (project),
+ "__variables_treeview");
+ selection = gtk_tree_view_get_selection (treeview);
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, COL_VAR_VALUE,
+ arg2, -1);
+ gtk_tree_model_get (model, &iter, COL_VAR_NAME, &variable_name, -1);
+
+ value = gbf_am_config_mapping_lookup (config, "variables");
+ if (!value)
+ {
+ variables = gbf_am_config_mapping_new ();
+ value = gbf_am_config_value_new (GBF_AM_TYPE_MAPPING);
+ gbf_am_config_value_set_mapping (value, variables);
+ gbf_am_config_mapping_insert (config, "variables", value);
+
+ }
+ else
+ {
+ variables = gbf_am_config_value_get_mapping (value);
+ }
+ value = gbf_am_config_mapping_lookup (variables, variable_name);
+
+ if (!value)
+ {
+ value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+ gbf_am_config_value_set_string (value, arg2);
+ gbf_am_config_mapping_insert (variables, variable_name, value);
+ }
+ else
+ {
+ gbf_am_config_value_set_string (value, arg2);
+ }
+ g_free (variable_name);
+}
+
+static void
+add_variable_clicked_cb (GtkWidget *button, GbfAmProject *project)
+{
+ GtkTreeView *treeview;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+
+ treeview = g_object_get_data (G_OBJECT (project),
+ "__variables_treeview");
+ model = gtk_tree_view_get_model (treeview);
+ gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, COL_VAR_NAME,
+ _("Enter new variable"), -1);
+
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(treeview), path,
+ NULL, FALSE, 0, 0);
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW(treeview), path,
+ gtk_tree_view_get_column(GTK_TREE_VIEW(treeview),
+ COL_VAR_NAME),
+ TRUE);
+ gtk_tree_path_free (path);
+}
+
+static void
+remove_variable_clicked_cb (GtkWidget *button, GtkWidget *top_level)
+{
+ GtkTreeView *treeview;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ const gchar *msg;
+ gchar *name;
+ GtkWidget *dlg;
+ GbfAmProject *project = g_object_get_data (G_OBJECT (top_level), "__project");
+ GbfAmConfigMapping *config = g_object_get_data (G_OBJECT (top_level), "__config");
+
+ treeview = g_object_get_data (G_OBJECT (project),
+ "__variables_treeview");
+ selection = gtk_tree_view_get_selection (treeview);
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+ gtk_tree_model_get (model, &iter, COL_VAR_NAME, &name, -1);
+ msg = _("Are you sure you want to remove variable \"%s\"?");
+ dlg = gtk_message_dialog_new_with_markup (NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_YES_NO,
+ msg, name);
+ if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_YES)
+ {
+ GbfAmConfigMapping *variables;
+ GbfAmConfigValue *value;
+
+ value = gbf_am_config_mapping_lookup (config, "variables");
+ if (value)
+ {
+ variables = gbf_am_config_value_get_mapping (value);
+ value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+ gbf_am_config_value_set_string (value, "");
+ gbf_am_config_mapping_insert (variables, name, value);
+ }
+ gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+ }
+ gtk_widget_destroy (dlg);
+ g_free (name);
+}
+
+static void
+variables_treeview_selection_changed_cb (GtkTreeSelection *selection,
+ GbfAmProject *project)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GtkWidget *remove_variable_button;
+
+ remove_variable_button = g_object_get_data (G_OBJECT (project),
+ "__remove_variable_button");
+
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_widget_set_sensitive (remove_variable_button, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (remove_variable_button, TRUE);
+ }
+}
+
+static void
+on_variables_hash_foreach (const gchar* variable_name, GbfAmConfigValue *value,
+ gpointer user_data)
+{
+ GtkTreeIter iter;
+ GtkListStore *store = (GtkListStore*) user_data;
+ const gchar *variable_value = gbf_am_config_value_get_string (value);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, COL_VAR_NAME, variable_name,
+ COL_VAR_VALUE, variable_value, -1);
+}
+
+static void
+on_project_widget_destroy (GtkWidget *wid, GtkWidget *top_level)
+{
+ GError *err = NULL;
+
+ GbfAmProject *project = g_object_get_data (G_OBJECT (top_level), "__project");
+ GbfAmConfigMapping *new_config = g_object_get_data (G_OBJECT (top_level), "__config");
+ gbf_am_project_set_config (project, new_config, &err);
+ if (err) {
+ g_warning ("%s", err->message);
+ g_error_free (err);
+ }
+ g_object_unref (top_level);
+}
+
+GtkWidget*
+gbf_am_properties_get_widget (GbfAmProject *project, GError **error)
+{
+ GladeXML *gxml;
+ GbfAmConfigMapping *config;
+ GbfAmConfigValue *value;
+ GtkWidget *top_level;
+ GtkWidget *table;
+ GtkWidget *treeview;
+ GtkWidget *add_module_button;
+ GtkWidget *add_package_button;
+ GtkWidget *remove_button;
+ GtkWidget *add_variable_button;
+ GtkWidget *remove_variable_button;
+ GtkTreeStore *store;
+ GtkListStore *variables_store;
+ GtkTreeSelection *selection;
+ GtkTreeViewColumn *col;
+ GtkCellRenderer *renderer;
+ const gchar *pkg_modules;
+ GError *err = NULL;
+ GbfAmConfigMapping *variables;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ config = gbf_am_project_get_config (project, &err);
+ if (err) {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+
+ gxml = glade_xml_new (GLADE_FILE, "project_properties_dialog",
+ GETTEXT_PACKAGE);
+ top_level = glade_xml_get_widget (gxml, "top_level");
+
+ g_object_set_data (G_OBJECT (top_level), "__project", project);
+ g_object_set_data_full (G_OBJECT (top_level), "__config", config,
+ (GDestroyNotify)gbf_am_config_mapping_destroy);
+ g_signal_connect (top_level, "destroy",
+ G_CALLBACK (on_project_widget_destroy), top_level);
+ g_object_ref (top_level);
+
+ add_module_button = glade_xml_get_widget (gxml, "add_module_button");
+ g_object_set_data (G_OBJECT (project), "__add_module_button",
+ add_module_button);
+
+ add_package_button = glade_xml_get_widget (gxml, "add_package_button");
+ g_object_set_data (G_OBJECT (project), "__add_package_button",
+ add_package_button);
+
+ remove_button = glade_xml_get_widget (gxml, "remove_button");
+ g_object_set_data (G_OBJECT (project), "__remove_button",
+ remove_button);
+
+ gtk_widget_set_sensitive (add_module_button, TRUE);
+ gtk_widget_set_sensitive (add_package_button, FALSE);
+ gtk_widget_set_sensitive (remove_button, FALSE);
+
+ table = glade_xml_get_widget (gxml, "general_properties_table");
+
+ g_object_ref (top_level);
+ gtk_container_remove (GTK_CONTAINER(top_level->parent), top_level);
+
+ g_signal_connect (add_module_button, "clicked",
+ G_CALLBACK (add_package_module_clicked_cb),
+ project);
+ g_signal_connect (add_package_button, "clicked",
+ G_CALLBACK (add_package_clicked_cb),
+ project);
+ g_signal_connect (remove_button, "clicked",
+ G_CALLBACK (remove_package_clicked_cb),
+ project);
+
+ /* Project Info */
+ add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+ _("Project:"), project->project_root_uri,
+ NULL, table, 0);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("Package name:"), NULL, "package_name",
+ table, 1);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("Version:"), NULL, "package_version",
+ table, 2);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("Url:"), NULL, "package_url",
+ table, 3);
+
+ /* pkg config packages */
+ store = gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
+ if ((value = gbf_am_config_mapping_lookup (config, "pkg_check_modules")) &&
+ (pkg_modules = gbf_am_config_value_get_string (value))) {
+ GtkTreeIter module_iter;
+ gchar **module;
+ gchar **modules = g_strsplit (pkg_modules, ", ", -1);
+ module = modules;
+ while (*module) {
+ GbfAmConfigValue *module_info;
+ GbfAmConfigMapping *module_info_map;
+
+ gchar *module_key = g_strconcat ("pkg_check_modules_",
+ *module,
+ NULL);
+
+ if ((module_info = gbf_am_config_mapping_lookup (config, module_key)) &&
+ (module_info_map = gbf_am_config_value_get_mapping (module_info))) {
+ GbfAmConfigValue *pkgs_val;
+ const gchar *packages;
+
+ gtk_tree_store_append (store, &module_iter, NULL);
+ gtk_tree_store_set (store, &module_iter, COL_PACKAGE, *module, -1);
+
+ if ((pkgs_val = gbf_am_config_mapping_lookup (module_info_map, "packages")) &&
+ (packages = gbf_am_config_value_get_string (pkgs_val))) {
+ gchar **pkgs, **pkg;
+ pkgs = g_strsplit (packages, ", ", -1);
+ pkg = pkgs;
+ while (*pkg) {
+ GtkTreeIter iter;
+ gchar *version;
+
+ gtk_tree_store_append (store, &iter, &module_iter);
+ if ((version = strchr (*pkg, ' '))) {
+ *version = '\0';
+ version++;
+ gtk_tree_store_set (store, &iter, COL_PACKAGE, *pkg,
+ COL_VERSION, version, -1);
+ } else {
+ gtk_tree_store_set (store, &iter, COL_PACKAGE, *pkg, -1);
+ }
+ pkg++;
+ }
+ g_strfreev (pkgs);
+ }
+ }
+ g_free (module_key);
+ module++;
+ }
+ g_strfreev (modules);
+ }
+
+ treeview = glade_xml_get_widget (gxml, "packages_treeview");
+
+ g_object_set_data (G_OBJECT (project), "__packages_treeview", treeview);
+ g_object_set_data (G_OBJECT (project), "__config", config);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW(treeview),
+ GTK_TREE_MODEL (store));
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+ g_signal_connect (G_OBJECT (renderer), "edited",
+ G_CALLBACK (package_edited_cb), top_level);
+ col = gtk_tree_view_column_new_with_attributes (_("Module/Packages"),
+ renderer,
+ "text", 0, NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+ g_signal_connect (G_OBJECT (renderer), "edited",
+ G_CALLBACK (package_version_edited_cb), top_level);
+ col = gtk_tree_view_column_new_with_attributes (_("Version"),
+ renderer,
+ "text", 1, NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
+ gtk_tree_view_expand_all (GTK_TREE_VIEW (treeview));
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (packages_treeview_selection_changed_cb),
+ project);
+
+ /* Set up variables list */
+ variables_store = gtk_list_store_new (N_VAR_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN);
+ if ((value = gbf_am_config_mapping_lookup (config, "variables")) &&
+ (variables = gbf_am_config_value_get_mapping (value))) {
+ gbf_am_config_mapping_foreach (variables,
+ on_variables_hash_foreach,
+ variables_store);
+ }
+
+ treeview = glade_xml_get_widget (gxml, "variables_treeview");
+ g_object_set_data (G_OBJECT (project), "__variables_treeview",
+ treeview);
+ gtk_tree_view_set_model (GTK_TREE_VIEW(treeview),
+ GTK_TREE_MODEL (variables_store));
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+ g_signal_connect (G_OBJECT (renderer), "edited",
+ G_CALLBACK (variable_name_edited_cb), top_level);
+ col = gtk_tree_view_column_new_with_attributes (_("Variable"),
+ renderer,
+ "text",
+ COL_VAR_NAME, NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+ g_signal_connect (G_OBJECT (renderer), "edited",
+ G_CALLBACK (variable_value_edited_cb), top_level);
+ col = gtk_tree_view_column_new_with_attributes (_("Value"),
+ renderer,
+ "text", COL_VAR_VALUE,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
+ gtk_tree_view_expand_all (GTK_TREE_VIEW (treeview));
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (variables_treeview_selection_changed_cb),
+ project);
+
+ add_variable_button = glade_xml_get_widget (gxml, "add_variable_button");
+ g_object_set_data (G_OBJECT (project), "__add_variable_button",
+ add_variable_button);
+
+ remove_variable_button = glade_xml_get_widget (gxml, "remove_variable_button");
+ g_object_set_data (G_OBJECT (project), "__remove_variable_button",
+ remove_variable_button);
+ gtk_widget_set_sensitive (add_variable_button, TRUE);
+ gtk_widget_set_sensitive (remove_variable_button, FALSE);
+
+ g_signal_connect (add_variable_button, "clicked",
+ G_CALLBACK (add_variable_clicked_cb),
+ project);
+ g_signal_connect (remove_variable_button, "clicked",
+ G_CALLBACK (remove_variable_clicked_cb),
+ top_level);
+
+ gtk_widget_show_all (top_level);
+
+ g_object_unref (variables_store);
+ g_object_unref (store);
+ g_object_unref (gxml);
+ return top_level;
+}
+
+enum
+{
+ COLUMN_CHECK,
+ COLUMN_MODULE_NAME
+};
+
+static const gchar* get_libs_key(GbfProjectTarget *target)
+{
+ g_return_val_if_fail (target != NULL, "ldadd");
+ /* We need to care here if it is ldadd or libadd #476315 */
+ if (g_str_equal (target->type, "shared_lib"))
+ {
+ return "libadd";
+ }
+ else
+ {
+ return "ldadd";
+ }
+}
+
+static void
+on_module_activate (GtkCellRendererToggle* cell_renderer,
+ const gchar* tree_path,
+ GtkTreeView* tree)
+{
+ GtkTreeIter iter;
+ GtkTreeModel* model = gtk_tree_view_get_model (tree);
+ gchar* module_name;
+ gchar* module_cflags;
+ gchar* module_libs;
+ gboolean use;
+ GtkTreePath* path = gtk_tree_path_new_from_string (tree_path);
+ GbfProjectTarget* target = g_object_get_data (G_OBJECT(tree), "target");
+ GbfAmConfigMapping* config = g_object_get_data (G_OBJECT(tree), "config");
+ GbfAmConfigMapping* group_config = g_object_get_data (G_OBJECT(tree), "group_config");
+ GbfAmConfigValue* am_cpp_value = gbf_am_config_mapping_lookup(group_config, "amcppflags");
+ GbfAmConfigValue* cpp_value = gbf_am_config_mapping_lookup(config, "cppflags");
+ GbfAmConfigValue* libs_value = gbf_am_config_mapping_lookup(config,
+ get_libs_key(target));
+
+ if (!cpp_value)
+ {
+ cpp_value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+ }
+ if (!libs_value)
+ {
+ libs_value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+ }
+ if (!am_cpp_value)
+ {
+ am_cpp_value = gbf_am_config_value_new (GBF_AM_TYPE_STRING);
+ }
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter,
+ COLUMN_MODULE_NAME, &module_name,
+ COLUMN_CHECK, &use,
+ -1);
+
+ module_cflags = g_strdup_printf ("$(%s_CFLAGS)", (gchar*) module_name);
+ module_libs = g_strdup_printf ("$(%s_LIBS)", (gchar*) module_name);
+ g_free (module_name);
+ gtk_tree_path_free (path);
+
+ if (!use)
+ {
+ GString* cppflags;
+ GString* am_cpp_flags;
+ GString* libs;
+ cppflags = g_string_new (gbf_am_config_value_get_string (cpp_value));
+ am_cpp_flags = g_string_new (gbf_am_config_value_get_string (am_cpp_value));
+ libs = g_string_new (gbf_am_config_value_get_string (libs_value));
+
+ if (strlen (cppflags->str) && !strstr (cppflags->str, module_cflags))
+ {
+ g_string_append_printf (cppflags, " %s", module_cflags);
+ gbf_am_config_value_set_string (cpp_value, cppflags->str);
+ gbf_am_config_mapping_insert (config, "cppflags", cpp_value);
+ }
+ else if (!strstr (am_cpp_flags->str, module_cflags))
+ {
+ g_string_append_printf (am_cpp_flags, " %s", module_cflags);
+ gbf_am_config_value_set_string (am_cpp_value, am_cpp_flags->str);
+ gbf_am_config_mapping_insert (group_config, "amcppflags", am_cpp_value);
+ }
+ if (!strstr (libs->str, module_libs))
+ {
+ g_string_append_printf (libs, " %s", module_libs);
+ gbf_am_config_value_set_string (libs_value, libs->str);
+ gbf_am_config_mapping_insert (config, get_libs_key (target), libs_value);
+ }
+
+ g_string_free (libs, TRUE);
+ g_string_free (cppflags, TRUE);
+ g_string_free (am_cpp_flags, TRUE);
+
+ }
+ else
+ {
+ const gchar* cpp_flags = cpp_value ? gbf_am_config_value_get_string(cpp_value) : NULL;
+ const gchar* am_cpp_flags = am_cpp_value ? gbf_am_config_value_get_string(am_cpp_value) : NULL;
+ const gchar* libs_flags = libs_value ? gbf_am_config_value_get_string(libs_value) : NULL;
+
+ if (cpp_flags && strlen (cpp_flags))
+ {
+ const gchar* start = strstr (cpp_flags, module_cflags);
+ GString* str = g_string_new (cpp_flags);
+ if (start)
+ g_string_erase (str, start - cpp_flags, strlen (module_cflags));
+ gbf_am_config_value_set_string (cpp_value, str->str);
+ g_string_free (str, TRUE);
+ gbf_am_config_mapping_insert (config, "cppflags", cpp_value);
+ }
+ else if (am_cpp_flags)
+ {
+ const gchar* start = strstr (am_cpp_flags, module_cflags);
+ GString* str = g_string_new (am_cpp_flags);
+ if (start)
+ g_string_erase (str, start - am_cpp_flags, strlen (module_cflags));
+ gbf_am_config_value_set_string (am_cpp_value, str->str);
+ g_string_free (str, TRUE);
+ gbf_am_config_mapping_insert (group_config, "amcppflags", am_cpp_value);
+ }
+ if (libs_flags)
+ {
+ const gchar* start = strstr (libs_flags, module_libs);
+ GString* str = g_string_new (libs_flags);
+ if (start)
+ g_string_erase (str, start - libs_flags, strlen (module_libs));
+ gbf_am_config_value_set_string (libs_value, str->str);
+ g_string_free (str, TRUE);
+ gbf_am_config_mapping_insert (config, get_libs_key (target), libs_value);
+ }
+ }
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_CHECK, !use, -1);
+ g_free (module_cflags);
+ g_free (module_libs);
+}
+
+static gboolean
+update_tree_foreach (GtkTreeModel* model,
+ GtkTreePath* path,
+ GtkTreeIter* iter,
+ GtkWidget* view)
+{
+ gchar* module;
+ gboolean use = FALSE;
+ GbfAmConfigMapping* config = g_object_get_data (G_OBJECT(view), "config");
+ GbfAmConfigMapping* group_config = g_object_get_data (G_OBJECT(view), "group_config");
+ GbfProjectTarget* target = g_object_get_data (G_OBJECT(view), "target");
+ GbfAmConfigValue* am_cpp_value = gbf_am_config_mapping_lookup(group_config, "amcppflags");
+ GbfAmConfigValue* cpp_value = gbf_am_config_mapping_lookup(config, "cppflags");
+ GbfAmConfigValue* libs_value = gbf_am_config_mapping_lookup(config, get_libs_key(target));
+ const gchar* cpp_flags = cpp_value ? gbf_am_config_value_get_string(cpp_value) : NULL;
+ const gchar* am_cpp_flags = am_cpp_value ? gbf_am_config_value_get_string(am_cpp_value) : NULL;
+ const gchar* libs_flags = libs_value ? gbf_am_config_value_get_string(libs_value) : NULL;
+
+ gchar* config_cflags;
+ gchar* config_libs;
+
+ gtk_tree_model_get (model, iter,
+ COLUMN_MODULE_NAME, &module,
+ -1);
+
+ config_cflags = g_strdup_printf ("$(%s_CFLAGS)", (gchar*) module);
+ config_libs = g_strdup_printf ("$(%s_LIBS)", (gchar*) module);
+ g_free (module);
+
+ if ((cpp_flags && strstr (cpp_flags, config_cflags)) ||
+ (am_cpp_flags && strstr (am_cpp_flags, config_cflags)))
+ {
+ if (libs_flags && strstr(libs_flags, config_libs))
+ use = TRUE;
+ }
+
+ gtk_list_store_set (GTK_LIST_STORE(model), iter,
+ COLUMN_CHECK, use,
+ -1);
+ g_free (config_cflags);
+ g_free (config_libs);
+
+ return FALSE; /* continue iteration */
+}
+
+static GtkWidget*
+create_module_list (GbfAmProject *project,
+ GbfProjectTarget *target,
+ GbfAmConfigMapping *config,
+ GbfAmConfigMapping *group_config)
+{
+ GtkWidget* view;
+ GtkListStore* list;
+ GList* modules;
+ GList* node;
+ GtkCellRenderer* renderer_text;
+ GtkCellRenderer* renderer_toggle;
+ GtkTreeViewColumn* column_text;
+ GtkTreeViewColumn* column_toggle;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+
+ list = gtk_list_store_new (2, G_TYPE_BOOLEAN, G_TYPE_STRING);
+ view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(list));
+ g_object_set_data (G_OBJECT(view), "config", config);
+ g_object_set_data (G_OBJECT(view), "group_config", group_config);
+ g_object_set_data (G_OBJECT(view), "target", target);
+ renderer_text = gtk_cell_renderer_text_new();
+ renderer_toggle = gtk_cell_renderer_toggle_new();
+ g_signal_connect (renderer_toggle, "toggled", G_CALLBACK (on_module_activate),
+ view);
+
+ column_toggle = gtk_tree_view_column_new_with_attributes (_("Use"),
+ renderer_toggle,
+ "active", COLUMN_CHECK,
+ NULL);
+
+ column_text = gtk_tree_view_column_new_with_attributes (_("Module"),
+ renderer_text,
+ "text", COLUMN_MODULE_NAME,
+ NULL);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (view), column_toggle);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (view), column_text);
+ gtk_widget_set_size_request (view, -1, 200);
+
+ modules = gbf_project_get_config_modules (GBF_PROJECT (project), NULL);
+
+ for (node = modules; node != NULL; node = g_list_next (node))
+ {
+ GtkTreeIter iter;
+
+ gtk_list_store_append (list, &iter);
+ gtk_list_store_set (list, &iter,
+ COLUMN_CHECK, FALSE,
+ COLUMN_MODULE_NAME, node->data,
+ -1);
+ }
+ gtk_tree_model_foreach (GTK_TREE_MODEL(list),
+ (GtkTreeModelForeachFunc) update_tree_foreach,
+ view);
+
+ return view;
+}
+
+static void
+on_group_widget_destroy (GtkWidget *wid, GtkWidget *table)
+{
+ GError *err = NULL;
+
+ GbfAmProject *project = g_object_get_data (G_OBJECT (table), "__project");
+ GbfAmConfigMapping *new_config = g_object_get_data (G_OBJECT (table), "__config");
+ const gchar *group_id = g_object_get_data (G_OBJECT (table), "__group_id");
+ gbf_am_project_set_group_config (project, group_id, new_config, &err);
+ if (err) {
+ g_warning ("%s", err->message);
+ g_error_free (err);
+ }
+ g_object_unref (table);
+}
+
+GtkWidget*
+gbf_am_properties_get_group_widget (GbfAmProject *project,
+ const gchar *group_id,
+ GError **error)
+{
+ GbfProjectGroup *group;
+ GbfAmConfigMapping *config;
+ GbfAmConfigValue *value;
+ GtkWidget *table;
+ GtkWidget *table_cflags;
+ GtkWidget *expander_cflags;
+ GError *err = NULL;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ group = gbf_project_get_group (GBF_PROJECT (project), group_id, &err);
+ if (err) {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+ config = gbf_am_project_get_group_config (project, group_id, &err);
+ if (err) {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+
+ g_return_val_if_fail (group != NULL, NULL);
+ g_return_val_if_fail (config != NULL, NULL);
+
+ table = gtk_table_new (7, 2, FALSE);
+ g_object_ref (table);
+ g_object_set_data (G_OBJECT (table), "__project", project);
+ g_object_set_data_full (G_OBJECT (table), "__config", config,
+ (GDestroyNotify)gbf_am_config_mapping_destroy);
+ g_object_set_data_full (G_OBJECT (table), "__group_id",
+ g_strdup (group_id),
+ (GDestroyNotify)g_free);
+ g_signal_connect (table, "destroy",
+ G_CALLBACK (on_group_widget_destroy), table);
+
+ /* Group name */
+ add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+ _("Group name:"), group->name, NULL, table, 0);
+ /* CFlags */
+ table_cflags = gtk_table_new(7,2, FALSE);
+ expander_cflags = gtk_expander_new(_("Advanced"));
+ gtk_table_attach (GTK_TABLE (table), expander_cflags, 0, 2, 2, 3,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+ gtk_container_add(GTK_CONTAINER(expander_cflags), table_cflags);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("C compiler flags:"), NULL, "amcflags", table_cflags, 0);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("C preprocessor flags:"), NULL, "amcppflags", table_cflags, 1);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("C++ compiler flags:"), NULL, "amcxxflags", table_cflags, 2);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("gcj compiler flags (ahead-of-time):"), NULL, "amgcjflags", table_cflags, 3);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("Java compiler flags (just-in-time):"), NULL, "amjavaflags", table_cflags, 4);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("Fortran compiler flags:"), NULL, "amfflags", table_cflags, 5);
+ /* Includes */
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("Includes (deprecated):"), NULL, "includes",
+ table_cflags, 6);
+
+ /* Install directories */
+ value = gbf_am_config_mapping_lookup (config, "installdirs");
+ if (value) {
+ GtkWidget *table2, *frame, *lab;
+ char *text;
+ frame = gtk_frame_new ("");
+
+ lab = gtk_frame_get_label_widget (GTK_FRAME(frame));
+ text = g_strdup_printf ("<b>%s</b>", _("Install directories:"));
+ gtk_label_set_markup (GTK_LABEL(lab), text);
+ g_free (text);
+
+ gtk_frame_set_shadow_type (GTK_FRAME (frame),
+ GTK_SHADOW_NONE);
+ gtk_widget_show (frame);
+ gtk_table_attach (GTK_TABLE (table), frame, 0, 2, 3, 4,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+ table2 = gtk_table_new (0, 0, FALSE);
+ gtk_widget_show (table2);
+ gtk_container_set_border_width (GTK_CONTAINER (table2), 5);
+ gtk_container_add (GTK_CONTAINER(frame), table2);
+ gbf_am_config_mapping_foreach (value->mapping,
+ recursive_config_foreach_cb,
+ table2);
+ }
+ gtk_widget_show_all (table);
+ gbf_project_group_free (group);
+ return table;
+}
+
+static void
+on_target_widget_destroy (GtkWidget *wid, GtkWidget *table)
+{
+ GError *err = NULL;
+
+ GbfAmProject *project = g_object_get_data (G_OBJECT (table), "__project");
+ GbfAmConfigMapping *new_config = g_object_get_data (G_OBJECT (table), "__config");
+ GbfAmConfigMapping *new_group_config = g_object_get_data (G_OBJECT (table), "__group_config");
+ const gchar *target_id = g_object_get_data (G_OBJECT (table), "__target_id");
+ const gchar *group_id = g_object_get_data (G_OBJECT (table), "__group_id");
+ gbf_am_project_set_target_config (project, target_id, new_config, &err);
+ if (err) {
+ g_warning ("%s", err->message);
+ g_error_free (err);
+ }
+ err = NULL;
+ gbf_am_project_set_group_config (project, group_id, new_group_config, &err);
+ if (err) {
+ g_warning ("%s", err->message);
+ g_error_free (err);
+ }
+ g_object_unref (table);
+}
+
+static void
+on_advanced_clicked (GtkButton* button,
+ GtkWidget* target_widget)
+{
+ GtkWidget* dialog;
+ GtkWidget* table_cflags;
+ const gchar* lib_type;
+ GbfAmProject* project;
+ GbfAmConfigMapping* config;
+ GbfProjectTarget* target;
+ GtkWidget* view;
+ GtkTreeModel* model;
+
+ project = g_object_get_data (G_OBJECT(target_widget), "__project");
+ config = g_object_get_data (G_OBJECT(target_widget), "__config");
+ target = g_object_get_data (G_OBJECT(target_widget), "__target");
+ view = g_object_get_data (G_OBJECT(target_widget), "__view");
+
+ table_cflags = gtk_table_new(9,2, FALSE);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("C compiler flags:"), NULL, "cflags", table_cflags, 0);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("C preprocessor flags"), NULL, "cppflags", table_cflags, 1);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("C++ compiler flags"), NULL, "cxxflags", table_cflags, 2);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("gcj compiler flags (ahead-of-time)"), NULL, "gcjflags", table_cflags, 3);
+ add_configure_property (project, config, GBF_AM_CONFIG_ENTRY,
+ _("Fortran compiler flags:"), NULL, "amfflags", table_cflags, 4);
+ /* LDFLAGS */
+ add_configure_property (project, config,
+ GBF_AM_CONFIG_ENTRY,
+ _("Linker flags:"), NULL,
+ "ldflags", table_cflags, 6);
+
+ lib_type = get_libs_key (target);
+ add_configure_property (project, config,
+ GBF_AM_CONFIG_ENTRY,
+ _("Libraries:"), NULL,
+ lib_type, table_cflags, 7);
+
+ /* DEPENDENCIES */
+ add_configure_property (project, config,
+ GBF_AM_CONFIG_ENTRY,
+ _("Dependencies:"), NULL,
+ "explicit_deps", table_cflags, 8);
+
+ dialog = gtk_dialog_new_with_buttons (_("Advanced options"),
+ NULL,
+ GTK_DIALOG_MODAL,
+ GTK_STOCK_CLOSE,
+ GTK_RESPONSE_CLOSE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), table_cflags);
+ gtk_widget_show_all (dialog);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ /* Little hack to reuse func... */
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) update_tree_foreach,
+ view);
+}
+
+GtkWidget*
+gbf_am_properties_get_target_widget (GbfAmProject *project,
+ const gchar *target_id, GError **error)
+{
+ GbfProjectGroup *group;
+ GbfAmConfigMapping *group_config;
+ GbfProjectTarget *target;
+ GbfAmConfigMapping *config;
+ GbfAmConfigValue *value;
+ GbfAmConfigMapping *installdirs;
+ GbfAmConfigValue *installdirs_val, *dir_val;
+ GtkWidget *table;
+ GError *err = NULL;
+
+ g_return_val_if_fail (GBF_IS_AM_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ target = gbf_project_get_target (GBF_PROJECT (project), target_id, &err);
+ if (err) {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+ config = gbf_am_project_get_target_config (project, target_id, &err);
+ if (err) {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+ g_return_val_if_fail (target != NULL, NULL);
+ g_return_val_if_fail (config != NULL, NULL);
+
+ group = gbf_project_get_group (GBF_PROJECT (project),
+ target->group_id, NULL);
+ group_config = gbf_am_project_get_group_config (project,
+ target->group_id,
+ NULL);
+ table = gtk_table_new (9, 2, FALSE);
+ g_object_ref (table);
+ g_object_set_data (G_OBJECT (table), "__project", project);
+ g_object_set_data_full (G_OBJECT (table), "__config", config,
+ (GDestroyNotify)gbf_am_config_mapping_destroy);
+ g_object_set_data_full (G_OBJECT (table), "__group_config", group_config,
+ (GDestroyNotify)gbf_am_config_mapping_destroy);
+ g_object_set_data_full (G_OBJECT (table), "__target_id",
+ g_strdup (target_id),
+ (GDestroyNotify)g_free);
+ g_object_set_data_full (G_OBJECT (table), "__group_id",
+ g_strdup(group->id),
+ (GDestroyNotify)g_free);
+ g_object_set_data_full (G_OBJECT (table), "__target",
+ target,
+ (GDestroyNotify)gbf_project_target_free);
+ g_signal_connect (table, "destroy",
+ G_CALLBACK (on_target_widget_destroy), table);
+
+ /* Target name */
+ add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+ _("Target name:"), target->name, NULL, table, 0);
+ /* Target type */
+ add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+ _("Type:"),
+ gbf_project_name_for_type (GBF_PROJECT (project),
+ target->type),
+ NULL, table, 1);
+ /* Target group */
+ add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+ _("Group:"), group->name,
+ NULL, table, 2);
+
+ /* Target primary */
+ /* FIXME: Target config 'installdir' actually stores target primary,
+ * and not what it really seems to mean, i.e the primary prefix it
+ * belongs to. The actual install directory of a target is the
+ * install directory of primary prefix it belongs to and is
+ * configured in group properties.
+ */
+ value = gbf_am_config_mapping_lookup (config, "installdir");
+ installdirs_val = gbf_am_config_mapping_lookup (group_config,
+ "installdirs");
+ if (installdirs_val)
+ installdirs = gbf_am_config_value_get_mapping (installdirs_val);
+
+ if (!value || !installdirs_val) {
+ add_configure_property (project, config, GBF_AM_CONFIG_LABEL,
+ _("Install directory:"), NULL, "installdir",
+ table, 3);
+ } else {
+ const gchar *primary_prefix;
+ gchar *installdir;
+
+ primary_prefix = gbf_am_config_value_get_string (value);
+ installdirs = gbf_am_config_value_get_mapping (installdirs_val);
+ dir_val = gbf_am_config_mapping_lookup (installdirs,
+ primary_prefix);
+ if (dir_val) {
+ installdir = g_strconcat (primary_prefix, " = ",
+ gbf_am_config_value_get_string (dir_val),
+ NULL);
+ add_configure_property (project, config,
+ GBF_AM_CONFIG_LABEL,
+ _("Install directory:"),
+ installdir, NULL,
+ table, 3);
+ g_free (installdir);
+ } else {
+ add_configure_property (project, config,
+ GBF_AM_CONFIG_LABEL,
+ _("Install directory:"),
+ NULL, "installdir",
+ table, 3);
+ }
+ }
+
+ if (target->type && (strcmp (target->type, "program") == 0 ||
+ strcmp (target->type, "shared_lib") == 0 ||
+ strcmp (target->type, "static_lib") == 0)) {
+ GtkWidget* scrolled_window;
+ GtkWidget* view = create_module_list(project, target,
+ config, group_config);
+ GtkWidget* button = gtk_button_new_with_label (_("Advanced..."));
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_container_add (GTK_CONTAINER(scrolled_window), view);
+ gtk_table_attach (GTK_TABLE (table), scrolled_window, 0, 2, 4, 5,
+ GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 5, 3);
+ gtk_table_attach (GTK_TABLE (table), button, 0, 2, 5, 6,
+ GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 5, 3);
+ g_object_set_data (G_OBJECT (table), "__view", view);
+ g_signal_connect (button, "clicked", G_CALLBACK(on_advanced_clicked), table);
+ }
+ gtk_widget_show_all (table);
+ return table;
+}
Added: trunk/plugins/gbf-am/gbf-am-properties.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am-properties.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,38 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-am-properties.h
+ *
+ * Copyright (C) 2005 Naba Kumar
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Naba Kumar
+ */
+
+#ifndef _GBF_AM_PROPERTIES_H_
+#define _GBF_AM_PROPERTIES_H_
+
+#include <gtk/gtkwidget.h>
+#include "gbf-am-project.h"
+
+GtkWidget *gbf_am_properties_get_widget (GbfAmProject *project, GError **error);
+GtkWidget *gbf_am_properties_get_group_widget (GbfAmProject *project,
+ const gchar *group_id,
+ GError **error);
+GtkWidget *gbf_am_properties_get_target_widget (GbfAmProject *project,
+ const gchar *target_id,
+ GError **error);
+
+#endif
Added: trunk/plugins/gbf-am/gbf-am.plugin.in
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/gbf-am.plugin.in Wed Dec 31 11:55:52 2008
@@ -0,0 +1,11 @@
+[Anjuta Plugin]
+_Name=Autotools backend
+_Description=Autotools backend for project manager
+Location=gbf-am:GbfAmPlugin
+Icon=gbf-am-plugin-48.png
+UserActivatable=no
+Interfaces=IAnjutaProjectBackend
+Dependencies=anjuta-project-manager:ProjectManagerPlugin
+
+[Project]
+Supported-Project-Types=automake
Added: trunk/plugins/gbf-am/output.dtd
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/output.dtd Wed Dec 31 11:55:52 2008
@@ -0,0 +1,39 @@
+<!DOCTYPE project [
+
+<!ELEMENT project (config?,group+)>
+<!ATTLIST project
+ root CDATA #REQUIRED>
+
+<!ELEMENT config (param*)>
+
+<!ELEMENT param (item*)>
+<!ATTLIST param
+ name CDATA #REQUIRED
+ value CDATA #IMPLIED>
+
+<!ELEMENT item EMPTY>
+<!ATTLIST item
+ name CDATA #IMPLIED
+ value CDATA #REQUIRED>
+
+<!ELEMENT group (config?,group*,target*)>
+<!ATTLIST group
+ id CDATA #REQUIRED
+ name CDATA #REQUIRED>
+
+<!ELEMENT target (config?,source*,dependency*)>
+<!ATTLIST target
+ id CDATA #REQUIRED
+ name CDATA #REQUIRED
+ type CDATA #REQUIRED>
+
+<!ELEMENT source EMPTY>
+<!ATTLIST source
+ uri CDATA #REQUIRED>
+
+<!ELEMENT dependency EMPTY>
+<!ATTLIST dependency
+ file CDATA #REQUIRED
+ target CDATA #REQUIRED>
+
+]>
Added: trunk/plugins/gbf-am/plugin.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/plugin.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,123 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ plugin.c
+ Copyright (C) 2008 SÃbastien Granjoux
+
+ 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 2 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#define DEBUG
+#include <config.h>
+#include <libanjuta/anjuta-debug.h>
+#include <libanjuta/gbf-project.h>
+#include <libanjuta/interfaces/ianjuta-project-backend.h>
+
+#include "plugin.h"
+#include "gbf-am-project.h"
+
+
+#define ICON_FILE "gfb-am-plugin-48.png"
+
+/* AnjutaPlugin functions
+ *---------------------------------------------------------------------------*/
+
+static gboolean
+activate_plugin (AnjutaPlugin *plugin)
+{
+ DEBUG_PRINT ("GbfAmPlugin: Activating Gnome build am backend Plugin ...");
+
+ return TRUE;
+}
+
+static gboolean
+deactivate_plugin (AnjutaPlugin *plugin)
+{
+ DEBUG_PRINT ("GbfAmPlugin: Deacctivating Gnome build am backend Plugin ...");
+ return TRUE;
+}
+
+
+/* IAnjutaProjectBackend implementation
+ *---------------------------------------------------------------------------*/
+
+static GbfProject*
+iproject_backend_new_project (IAnjutaProjectBackend* backend, GError** err)
+{
+ GbfProject *project;
+
+ project = gbf_am_project_new ();
+
+ return project;
+}
+
+static void
+iproject_backend_iface_init(IAnjutaProjectBackendIface *iface)
+{
+ iface->new_project = iproject_backend_new_project;
+}
+
+/* GObject functions
+ *---------------------------------------------------------------------------*/
+
+/* Used in dispose and finalize */
+static gpointer parent_class;
+
+static void
+gbf_am_plugin_instance_init (GObject *obj)
+{
+}
+
+/* dispose is used to unref object created with instance_init */
+
+static void
+dispose (GObject *obj)
+{
+ G_OBJECT_CLASS (parent_class)->dispose (obj);
+}
+
+/* finalize used to free object created with instance init */
+
+static void
+finalize (GObject *obj)
+{
+ G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static void
+gbf_am_plugin_class_init (GObjectClass *klass)
+{
+ AnjutaPluginClass *plugin_class = ANJUTA_PLUGIN_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ plugin_class->activate = activate_plugin;
+ plugin_class->deactivate = deactivate_plugin;
+ klass->dispose = dispose;
+ klass->finalize = finalize;
+}
+
+/* AnjutaPlugin declaration
+ *---------------------------------------------------------------------------*/
+
+ANJUTA_PLUGIN_BEGIN (GbfAmPlugin, gbf_am_plugin);
+ANJUTA_PLUGIN_ADD_INTERFACE (iproject_backend, IANJUTA_TYPE_PROJECT_BACKEND);
+ANJUTA_PLUGIN_END;
+
+G_MODULE_EXPORT void
+anjuta_glue_register_components (GTypeModule *module)
+{
+ gbf_am_plugin_get_type (module);
+ gbf_am_project_get_type (module);
+}
Added: trunk/plugins/gbf-am/plugin.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/plugin.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,47 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ plugin.h
+ Copyright (C) 2008 SÃbastien Granjoux
+
+ 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 2 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef __PLUGIN_H__
+#define __PLUGIN_H__
+
+#include <libanjuta/anjuta-plugin.h>
+
+extern GType gbf_am_plugin_get_type (GTypeModule *module);
+#define GBF_TYPE_PLUGIN_AM (gbf_am_plugin_get_type (NULL))
+#define GBF_PLUGIN_AM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GBF_TYPE_PLUGIN_AM, GbfAmPlugin))
+#define GBF_PLUGIN_AM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GBF_TYPE_PLUGIN_AM, GbfAmPluginClass))
+#define GBF_IS_PLUGIN_AM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GBF_TYPE_PLUGIN_AM))
+#define GBF_IS_PLUGIN_AM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GBF_TYPE_PLUGIN_AM))
+#define GBF_PLUGIN_AM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GBF_TYPE_PLUGIN_AM, GbfAmPluginClass))
+
+typedef struct _GbfAmPlugin GbfAmPlugin;
+typedef struct _GbfAmPluginClass GbfAmPluginClass;
+
+struct _GbfAmPlugin
+{
+ AnjutaPlugin parent;
+};
+
+struct _GbfAmPluginClass
+{
+ AnjutaPluginClass parent_class;
+};
+
+#endif
Added: trunk/plugins/gbf-am/program.xpm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/program.xpm Wed Dec 31 11:55:52 2008
@@ -0,0 +1,56 @@
+/* XPM */
+static char * program_xpm[] = {
+"14 14 39 1",
+" c None",
+". c #000000",
+"+ c #E4E3E1",
+"@ c #E4E4E4",
+"# c #B3B3B1",
+"$ c #484641",
+"% c #9F9D96",
+"& c #888781",
+"* c #B0AFAD",
+"= c #A8A7A1",
+"- c #908E86",
+"; c #97958E",
+"> c #807D74",
+", c #595854",
+"' c #605E57",
+") c #898883",
+"! c #76746B",
+"~ c #43423F",
+"{ c #282724",
+"] c #363430",
+"^ c #6D6B63",
+"/ c #E2E2E1",
+"( c #B6B5AF",
+"_ c #21201E",
+": c #0A0908",
+"< c #181816",
+"[ c #E6E6E4",
+"} c #65635C",
+"| c #161614",
+"1 c #8C8B89",
+"2 c #DFDEDC",
+"3 c #B0AFA9",
+"4 c #D5D4D1",
+"5 c #93918B",
+"6 c #D6D5D2",
+"7 c #ABA9A3",
+"8 c #5D5C55",
+"9 c #494943",
+"0 c #42413C",
+" ",
+" ... ",
+" .. .+. .. ",
+" # $%$ &.. ",
+" .*=%%%-;>$. ",
+" .%%,')>!. ",
+" ..$%'~{]%^$..",
+" ./(%,_:<[>}'.",
+" ..$%)]|12}$..",
+" .3>([45}. ",
+" .67%>>!!8$. ",
+" ..,.$!$.9.. ",
+" .. .0. .. ",
+" ... "};
Added: trunk/plugins/gbf-am/run-test.sh
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/run-test.sh Wed Dec 31 11:55:52 2008
@@ -0,0 +1,82 @@
+#!/bin/sh
+
+project_root=`mktemp -d /tmp/test-am-XXXXXX`
+
+echo Creating project in $project_root
+
+if ! pushd $project_root > /dev/null; then
+ echo Failed to cd to the project directory
+fi
+
+# Create mandatory automake files
+echo -n Creating NEWS AUTHORS INSTALL README COPYING ChangeLog
+touch NEWS AUTHORS INSTALL README COPYING ChangeLog
+
+echo -n configure.in
+cat > configure.in <<EOF
+AC_PREREQ(2.52)
+AC_INIT(test-am, 0.1)
+AM_CONFIG_HEADER(config.h)
+AC_CONFIG_SRCDIR(src/Makefile.am)
+AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
+
+AC_OUTPUT([
+Makefile
+src/Makefile
+])
+
+EOF
+
+echo -n Makefile.am
+cat > Makefile.am <<EOF
+SUBDIRS = src
+
+EOF
+
+echo
+
+# Create at least one directory
+echo -n Creating src/
+mkdir src
+cd src
+
+echo -n src/test.c src/test.h src/foo.c
+touch test.c test.h foo.c
+
+echo -n src/Makefile.am
+cat > Makefile.am <<EOF
+INCLUDES = -DFOO
+
+lib_LTLIBRARIES = libfoo.la
+
+libfoo_la_SOURCES = test.c test.h
+
+include_HEADERS = test.h
+
+bin_PROGRAMS = foo
+
+foo_SOURCES = \
+ foo.c
+
+foo_LDADD = libfoo.la
+
+EOF
+
+echo
+if test "$1" = "-n" -o "$1" = "--no-test"; then
+ exit 0;
+fi
+
+echo Now running test program...
+
+popd > /dev/null
+echo ./test $project_root
+libtool --mode=execute ./test $project_root
+
+if [ $? -eq 0 ]; then
+ echo Removing test project
+ rm -Rf $project_root
+ echo DONE!
+ echo
+fi
+
Added: trunk/plugins/gbf-am/shared.xpm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/shared.xpm Wed Dec 31 11:55:52 2008
@@ -0,0 +1,70 @@
+/* XPM */
+static char * shared_xpm[] = {
+"16 16 51 1",
+" c None",
+". c #000000",
+"+ c #B2A97E",
+"@ c #B6AD81",
+"# c #7A7356",
+"$ c #B0A77C",
+"% c #B5AC80",
+"& c #BAB184",
+"* c #BBB284",
+"= c #B6AD80",
+"- c #ADA57B",
+"; c #B7AE81",
+"> c #BDB486",
+", c #837C5C",
+"' c #E9E9D6",
+") c #B4AC80",
+"! c #7C7557",
+"~ c #C0B687",
+"{ c #BEB586",
+"] c #050504",
+"^ c #EAEADA",
+"/ c #B1A87D",
+"( c #B9B082",
+"_ c #C0B788",
+": c #6B6B4F",
+"< c #E9E9D5",
+"[ c #BEBE9C",
+"} c #1C1C15",
+"| c #0E0E0B",
+"1 c #E9E9D7",
+"2 c #E8E8D5",
+"3 c #BAB183",
+"4 c #808065",
+"5 c #E6E6D1",
+"6 c #E4E4CC",
+"7 c #E8E8D4",
+"8 c #CDCDB2",
+"9 c #6A6A4E",
+"0 c #E0E0C7",
+"a c #A8A87E",
+"b c #EAEAD8",
+"c c #E7E7D2",
+"d c #C5C5A4",
+"e c #E3E3CE",
+"f c #5D5D44",
+"g c #E7E7D3",
+"h c #434343",
+"i c #141414",
+"j c #E7E7D4",
+"k c #E5E5D1",
+"l c #E0E0CA",
+" ",
+" .... ",
+" + # ",
+" .$%&*=-. ",
+" ....;>,$.. ",
+" .'.-)!~{ ",
+" ..] .^../({_.. ",
+".:<[}|12.#.3#.4.",
+".'566'''7.2..68.",
+".90a..b5c7..def.",
+" ... .22g2.h..i ",
+" .gjkl. ",
+" ...... ",
+" ",
+" ",
+" "};
Added: trunk/plugins/gbf-am/static.xpm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/static.xpm Wed Dec 31 11:55:52 2008
@@ -0,0 +1,57 @@
+/* XPM */
+static char * static_xpm[] = {
+"16 16 38 1",
+" c None",
+". c #000000",
+"+ c #E9E9D6",
+"@ c #E8E8D5",
+"# c #050504",
+"$ c #EAEADA",
+"% c #484848",
+"& c #0B0B0A",
+"* c #050505",
+"= c #0D0D0D",
+"- c #6B6B4F",
+"; c #E9E9D5",
+"> c #BEBE9C",
+", c #1C1C15",
+"' c #0E0E0B",
+") c #E9E9D7",
+"! c #0E0E0C",
+"~ c #070706",
+"{ c #9F9F86",
+"] c #E8E8D4",
+"^ c #808065",
+"/ c #E6E6D1",
+"( c #E4E4CC",
+"_ c #E7E7D3",
+": c #CDCDB2",
+"< c #6A6A4E",
+"[ c #E0E0C7",
+"} c #A8A87E",
+"| c #EAEAD8",
+"1 c #E7E7D2",
+"2 c #C5C5A4",
+"3 c #E3E3CE",
+"4 c #5D5D44",
+"5 c #434343",
+"6 c #141414",
+"7 c #E7E7D4",
+"8 c #E5E5D1",
+"9 c #E0E0CA",
+" ",
+" ",
+" ",
+" ",
+" ...... ",
+" .+@@@. ",
+" ..# .$+++.%&*= ",
+".-;>,')@+ !~{]^ ",
+".+/((+++]_ ++(:.",
+".<[}..|/1]..234.",
+" ... .@@_ 5 6 ",
+" ._789. ",
+" ...... ",
+" ",
+" ",
+" "};
Added: trunk/plugins/gbf-am/test.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/test.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,221 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <unistd.h>
+#include <gtk/gtk.h>
+#include <libgnome/gnome-init.h>
+#include "gbf-am-project.h"
+
+static void
+source_print (GbfProject *project, GbfProjectTargetSource *source, gint level)
+{
+ gchar *indent = g_strnfill (level * 3, ' ');
+
+ g_assert (source != NULL);
+
+ g_print ("%s%s\n", indent, source->source_uri);
+
+ g_free (indent);
+}
+
+static void
+target_print (GbfProject *project, GbfProjectTarget *target, gint level)
+{
+ GList *iter;
+ gchar *indent = g_strnfill (level * 3, ' ');
+
+ g_assert (target != NULL);
+
+ g_print ("%s%s [%s]\n", indent, target->name, target->type);
+ for (iter = target->sources; iter; iter = g_list_next (iter)) {
+ GbfProjectTargetSource *source;
+ source = gbf_project_get_source (project, iter->data, NULL);
+ source_print (project, source, level + 1);
+ gbf_project_target_source_free (source);
+ }
+
+ g_free (indent);
+}
+
+static void
+group_print (GbfProject *project, GbfProjectGroup *group, gint level)
+{
+ GList *iter;
+ gchar *indent = g_strnfill (level * 3, ' ');
+
+ g_assert (group != NULL);
+
+ g_print ("%s%s\n", indent, group->name);
+ for (iter = group->groups; iter; iter = g_list_next (iter)) {
+ GbfProjectGroup *subgroup;
+ subgroup = gbf_project_get_group (project, iter->data, NULL);
+ group_print (project, subgroup, level + 1);
+ gbf_project_group_free (subgroup);
+ }
+
+ for (iter = group->targets; iter; iter = g_list_next (iter)) {
+ GbfProjectTarget *target;
+ target = gbf_project_get_target (project, iter->data, NULL);
+ target_print (project, target, level + 1);
+ gbf_project_target_free (target);
+ }
+
+ g_free (indent);
+}
+
+static void
+project_print (GbfProject *project)
+{
+ GbfProjectGroup *group;
+
+ group = gbf_project_get_group (project, "/", NULL);
+ group_print (project, group, 0);
+ gbf_project_group_free (group);
+}
+
+static void
+project_updated_cb (GbfProject *project, gpointer user_data)
+{
+ g_print ("----------========== PROJECT UPDATED ==========----------\n");
+ project_print (project);
+ g_print ("----------=====================================----------\n");
+}
+
+#define ERROR_CHECK(err) G_STMT_START { \
+ if ((err) != NULL) { \
+ g_print ("! Last operation failed: %s\n", (err)->message); \
+ g_error_free (err); \
+ (err) = NULL; \
+ goto out; \
+ } \
+} G_STMT_END;
+
+int
+main (int argc, char *argv[])
+{
+ GbfProject *project;
+ gboolean valid;
+ GList *list;
+ gchar *dir;
+ gchar *new_group_id, *new_target_id, *new_source_id;
+ GError *error = NULL;
+
+ /* Bootstrap */
+ gnome_program_init ("libgbf-am-test", VERSION, LIBGNOME_MODULE,
+ argc, argv, NULL,
+ NULL); /* Avoid GCC sentinel warning */
+
+ if (argc < 2) {
+ g_print ("! You need to specify a project path\n");
+ return 0;
+ }
+ dir = argv [1];
+
+ g_print ("+ Creating new gbf-am project\n");
+ project = gbf_am_project_new ();
+ if (!project) {
+ g_print ("! Project creation failed\n");
+ return 0;
+ }
+ g_signal_connect (project, "project-updated",
+ G_CALLBACK (project_updated_cb), NULL);
+
+ g_print ("+ Probing location: ");
+ valid = gbf_project_probe (project, dir, &error);
+ g_print ("%s\n", valid ? "ok" : "not an automake project");
+ ERROR_CHECK (error);
+
+ if (!valid)
+ goto out;
+
+ g_print ("+ Loading project %s\n\n", dir);
+ gbf_project_load (project, dir, &error);
+ ERROR_CHECK (error);
+
+ /* Show the initial project structure */
+ g_print ("+ Project structure:\n\n");
+ project_print (project);
+ g_print ("\n");
+
+ /* Test remaining elements accessors */
+ g_print ("+ Testing getters:\n\n");
+
+ g_print ("- All targets:\n");
+ list = gbf_project_get_all_targets (project, NULL);
+ while (list) {
+ g_print ("%s\n", (gchar *) list->data);
+ g_free (list->data);
+ list = g_list_delete_link (list, list);
+ }
+ g_print ("\n");
+
+ g_print ("- All sources:\n");
+ list = gbf_project_get_all_sources (project, NULL);
+ while (list) {
+ g_print ("%s\n", (gchar *) list->data);
+ g_free (list->data);
+ list = g_list_delete_link (list, list);
+ }
+ g_print ("\n");
+
+ /* Test adders and removers */
+ g_print ("+ Adding a group \"test\" in \"/\"\n");
+ new_group_id = gbf_project_add_group (project, "/", "test", &error);
+ ERROR_CHECK (error);
+ g_print ("+ Got group id \"%s\"\n\n", new_group_id);
+
+ g_print ("+ Adding a \"program\" target \"test\" in \"%s\"\n", new_group_id);
+ new_target_id = gbf_project_add_target (project, new_group_id,
+ "test", "program", &error);
+ ERROR_CHECK (error);
+ g_print ("+ Got target id \"%s\"\n\n", new_target_id);
+
+ g_print ("+ Adding a source \"test.c\" to target \"%s\"\n", new_target_id);
+ new_source_id = gbf_project_add_source (project, new_target_id, "test.c", &error);
+ ERROR_CHECK (error);
+ g_print ("+ Got source id \"%s\"\n\n", new_source_id);
+
+ g_print ("+ Removing the source \"%s\"\n\n", new_source_id);
+ gbf_project_remove_source (project, new_source_id, &error);
+ ERROR_CHECK (error);
+
+#if 0
+ g_print ("+ Removing the target \"%s\"\n\n", new_target_id);
+ gbf_project_remove_target (project, new_target_id, &error);
+ ERROR_CHECK (error);
+#endif
+
+#if 0
+ g_print ("+ Removing the group \"%s\"\n\n", new_group_id);
+ gbf_project_remove_group (project, new_group_id, &error);
+ ERROR_CHECK (error);
+#endif
+ g_free (new_group_id);
+ g_free (new_target_id);
+ g_free (new_source_id);
+
+ /* Test file monitoring: we need to enter the main loop for
+ * the signal to be emitted */
+ sleep (1);
+ g_print ("+ Testing file monitoring:\n\n");
+ {
+ gchar *file, *cmd;
+ file = g_build_filename (dir, "configure.in", NULL);
+ cmd = g_strdup_printf ("touch %s", file);
+ g_spawn_command_line_sync (cmd, NULL, NULL, NULL, NULL);
+ g_free (cmd);
+ g_free (file);
+ }
+
+ g_timeout_add (3000, (GSourceFunc) gtk_main_quit, NULL);
+ gtk_main ();
+
+ out:
+ g_print ("+ Unreffing project\n\n");
+ g_object_unref (project);
+
+ return 0;
+}
+
Added: trunk/plugins/gbf-am/unknown.xpm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-am/unknown.xpm Wed Dec 31 11:55:52 2008
@@ -0,0 +1,119 @@
+/* XPM */
+static char * unknown_xpm[] = {
+"16 16 100 2",
+" c None",
+". c #B4A889",
+"+ c #B2A688",
+"@ c #A89D80",
+"# c #9B9176",
+"$ c #9A9075",
+"% c #8F866D",
+"& c #B6AA8B",
+"* c #FBF9EB",
+"= c #E8E4D5",
+"- c #E3DECF",
+"; c #EEE9D8",
+"> c #ECE5D4",
+", c #938A71",
+"' c #AFA486",
+") c #E8E3D5",
+"! c #E2DECE",
+"~ c #DDD8C8",
+"{ c #D8D3C2",
+"] c #D3CDBC",
+"^ c #BAB39F",
+"/ c #E2DDCE",
+"( c #DCD8C8",
+"_ c #D7D2C1",
+": c #D2CDBB",
+"< c #CDC7B5",
+"[ c #DCD7C7",
+"} c #D7D1C1",
+"| c #D1CCBB",
+"1 c #CCC6B4",
+"2 c #C7C1AE",
+"3 c #C2BBA8",
+"4 c #BCB6A2",
+"5 c #A9A390",
+"6 c #837B64",
+"7 c #D6D1C0",
+"8 c #D1CBBA",
+"9 c #CBC6B4",
+"0 c #C6C0AE",
+"a c #C1BBA7",
+"b c #BCB5A1",
+"c c #B6B09B",
+"d c #A29B88",
+"e c #D0CAB9",
+"f c #CBC5B3",
+"g c #C6BFAD",
+"h c #C0BAA7",
+"i c #BBB4A1",
+"j c #B6AF9A",
+"k c #B1A994",
+"l c #9C9682",
+"m c #CAC4B2",
+"n c #C5BFAC",
+"o c #C0B9A6",
+"p c #BAB4A0",
+"q c #B5AE9A",
+"r c #B0A994",
+"s c #ABA38D",
+"t c #97907B",
+"u c #C4BEAB",
+"v c #BFB9A5",
+"w c #B5AE99",
+"x c #AFA893",
+"y c #AAA28D",
+"z c #A59D87",
+"A c #928A75",
+"B c #BEB8A4",
+"C c #B9B29E",
+"D c #B4AD98",
+"E c #AFA792",
+"F c #A9A28C",
+"G c #A49C86",
+"H c #9F9780",
+"I c #8D8570",
+"J c #B8B29D",
+"K c #B3AC97",
+"L c #AEA791",
+"M c #A9A18B",
+"N c #A39C85",
+"O c #9E967F",
+"P c #999179",
+"Q c #877F69",
+"R c #B3AB97",
+"S c #ADA690",
+"T c #A8A08A",
+"U c #A39B84",
+"V c #9E957E",
+"W c #8F8871",
+"X c #918971",
+"Y c #837B63",
+"Z c #9B9377",
+"` c #9E9784",
+" . c #99927E",
+".. c #948D78",
+"+. c #908872",
+"@. c #8B836D",
+"#. c #857E67",
+"$. c #888068",
+"%. c #847C65",
+"&. c #7F7760",
+" ",
+" . + @ @ # $ % ",
+" & * = - ; > , = % ",
+" ' ) ! ~ { ] % ^ ^ % ",
+" @ / ( _ : < % % % % ",
+" @ [ } | 1 2 3 4 5 6 ",
+" @ 7 8 9 0 a b c d 6 ",
+" @ e f g h i j k l 6 ",
+" @ m n o p q r s t 6 ",
+" @ u v ^ w x y z A 6 ",
+" @ B C D E F G H I 6 ",
+" @ J K L M N O P Q 6 ",
+" @ R S T U V W X Y 6 ",
+" Z ` + # $ Y 6 ",
+" 6 6 6 6 6 6 %.&. ",
+" "};
Added: trunk/plugins/gbf-mkfile/.cvsignore
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/.cvsignore Wed Dec 31 11:55:52 2008
@@ -0,0 +1,6 @@
+Makefile.in
+Makefile
+.deps
+.libs
+test
+gbf-mkfile-parse
Added: trunk/plugins/gbf-mkfile/GBF/.cvsignore
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/GBF/.cvsignore Wed Dec 31 11:55:52 2008
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Added: trunk/plugins/gbf-mkfile/GBF/General.pm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/GBF/General.pm Wed Dec 31 11:55:52 2008
@@ -0,0 +1,120 @@
+package GBF::General;
+
+use Exporter;
+use strict;
+
+our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+ ISA = qw (Exporter);
+
+ EXPORT = qw (&report_error &report_warning
+ &trim &canonicalize_name &empty_if_undef &expand_tabs
+ &base_name &dir_name &chop_backslash &max &min);
+ EXPORT_OK = qw ($debug);
+%EXPORT_TAGS = ();
+
+
+
+######################################################################
+#### SUBROUTINES AND HELPER FUNCTIONS ##############################
+######################################################################
+
+sub report_error
+{
+ my ($code, $message) = @_;
+
+ print STDERR "ERROR($code): $message\n";
+
+ return $code;
+}
+
+sub report_warning
+{
+ my ($code, $message) = @_;
+
+ print STDERR "WARNING($code): $message\n";
+}
+
+sub canonicalize_name
+{
+ my ($name);
+
+ $name = $_[0];
+ $name =~ tr/\.-/_/;
+
+ return $name;
+}
+
+sub trim
+{
+ $_ = $_[0];
+
+ s/^\s*//;
+ s/\s*$//;
+
+ return $_;
+}
+
+sub empty_if_undef
+{
+ return defined ($_[0]) ? $_[0] : "";
+}
+
+sub expand_tabs
+{
+ $_ = $_[0];
+ my $i;
+ while (/\t/) {
+ $i = index ($_, "\t");
+ $i = $i % 8;
+ if ($i == 0) {
+ s/\t/ /;
+ } else {
+ my $pad = " " x (8 - $i);
+ s/\t/$pad/;
+ };
+ };
+ return $_;
+}
+
+sub base_name {
+ my ($name, $dir);
+
+ $name = $_[0];
+ $dir = dir_name ($name);
+ $name =~ s/$dir//;
+
+ return $name;
+}
+
+sub dir_name {
+ my ($name);
+
+ $name = $_[0];
+ $name =~ s/[^\/]+$//;
+
+ return $name;
+}
+
+sub chop_backslash ($)
+{
+ $_ = shift;
+ s/\\\s*$//;
+ return $_;
+}
+
+sub max
+{
+ my $max = shift;
+ foreach my $x (@_) { $max = $x if $x > $max; };
+ return $max;
+}
+
+sub min
+{
+ my $min = shift;
+ foreach my $x (@_) { $min = $x if $x < $min; };
+ return $min;
+}
+
+1;
Added: trunk/plugins/gbf-mkfile/GBF/Make.pm
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/GBF/Make.pm Wed Dec 31 11:55:52 2008
@@ -0,0 +1,1321 @@
+package Make::Rule::Vars;
+use Carp;
+use strict;
+my $generation = 0; # lexical cross-package scope used!
+
+# Package to handle 'magic' variables pertaining to rules e.g. $@ $* $^ $?
+# by using tie to this package 'subsvars' can work with array of
+# hash references to possible sources of variable definitions.
+
+sub TIEHASH
+{
+ my ($class,$rule) = @_;
+ return bless \$rule,$class;
+}
+
+sub FETCH
+{
+ my $self = shift;
+ local $_ = shift;
+ my $rule = $$self;
+ return undef unless (/^[\ ^<?*]$/);
+ # print STDERR "FETCH $_ for ",$rule->Name,"\n";
+ return $rule->Name if ($_ eq '@');
+ return $rule->Base if ($_ eq '*');
+ return join(' ',$rule->exp_depend) if ($_ eq '^');
+ return join(' ',$rule->out_of_date) if ($_ eq '?');
+ # Next one is dubious - I think $< is really more subtle ...
+ return ($rule->exp_depend)[0] if ($_ eq '<');
+ return undef;
+}
+
+package Make::Rule;
+use Carp;
+use strict;
+
+# Bottom level 'rule' package
+# An instance exists for each ':' or '::' rule in the makefile.
+# The commands and dependancies are kept here.
+
+sub target
+{
+ return shift->{TARGET};
+}
+
+sub Name
+{
+ return shift->target->Name;
+}
+
+sub Base
+{
+ my $name = shift->target->Name;
+ $name =~ s/\.[^.]+$//;
+ return $name;
+}
+
+sub Info
+{
+ return shift->target->Info;
+}
+
+sub depend
+{
+ my $self = shift;
+ if (@_)
+ {
+ my $name = $self->Name;
+ my $dep = shift;
+ confess "dependants $dep are not an array reference" unless ('ARRAY' eq ref $dep);
+ my $file;
+ foreach $file (@$dep)
+ {
+ unless (exists $self->{DEPHASH}{$file})
+ {
+ $self->{DEPHASH}{$file} = 1;
+ push(@{$self->{DEPEND}},$file);
+ }
+ }
+ }
+ return (wantarray) ? @{$self->{DEPEND}} : $self->{DEPEND};
+}
+
+sub command
+{
+ my $self = shift;
+ if (@_)
+ {
+ my $cmd = shift;
+ confess "commands $cmd are not an array reference" unless ('ARRAY' eq ref $cmd);
+ if (@$cmd)
+ {
+ if (@{$self->{COMMAND}})
+ {
+ # warn "Command for ".$self->Name," redefined";
+ # print STDERR "Was:",join("\n",@{$self->{COMMAND}}),"\n";
+ # print STDERR "Now:",join("\n",@$cmd),"\n";
+ }
+ $self->{COMMAND} = $cmd;
+ }
+ else
+ {
+ if (@{$self->{COMMAND}})
+ {
+ # warn "Command for ".$self->Name," retained";
+ # print STDERR "Was:",join("\n",@{$self->{COMMAND}}),"\n";
+ }
+ }
+ }
+ return (wantarray) ? @{$self->{COMMAND}} : $self->{COMMAND};
+}
+
+#
+# The key make test - is target out-of-date as far as this rule is concerned
+# In scalar context - boolean value of 'do we need to apply the rule'
+# In list context the things we are out-of-date with e.g. magic $? variable
+#
+sub out_of_date
+{
+ my $array = wantarray;
+ my $self = shift;
+ my $info = $self->Info;
+ my @dep = ();
+ my $tdate = $self->target->date;
+ my $dep;
+ my $count = 0;
+ foreach $dep ($self->exp_depend)
+ {
+ my $date = $info->date($dep);
+ $count++;
+ if (!defined($date) || !defined($tdate) || $date < $tdate)
+ {
+ # warn $self->Name." ood wrt ".$dep."\n";
+ return 1 unless $array;
+ push(@dep,$dep);
+ }
+ }
+ return @dep if $array;
+ # Note special case of no dependencies means it is always out-of-date!
+ return !$count;
+}
+
+#
+# Return list of things rule depends on with variables expanded
+# - May need pathname and vpath processing as well
+#
+sub exp_depend
+{
+ my $self = shift;
+ my $info = $self->Info;
+ my @dep = map(split(/\s+/,$info->subsvars($_)),$self->depend);
+ return (wantarray) ? @dep : \ dep;
+}
+
+#
+# Return commands to apply rule with variables expanded
+# - No pathname processing needed, commands should always chdir()
+# to logical place (at least till we get very clever at bourne shell parsing).
+# - May need vpath processing
+#
+sub exp_command
+{
+ my $self = shift;
+ my $info = $self->Info;
+ my $base = $self->Name;
+ my %var;
+ tie %var,'Make::Rule::Vars',$self;
+ my @cmd = map($info->subsvars($_,\%var),$self->command);
+ return (wantarray) ? @cmd : \ cmd;
+}
+
+#
+# clone creates a new rule derived from an existing rule, but
+# with a different target. Used when left hand side was a variable.
+# perhaps should be used for dot/pattern rule processing too.
+#
+sub clone
+{
+ my ($self,$target) = @_;
+ my %hash = %$self;
+ $hash{TARGET} = $target;
+ $hash{DEPEND} = [ {$self->{DEPEND}}];
+ $hash{DEPHASH} = {%{$self->{DEPHASH}}};
+ my $obj = bless \%hash,ref $self;
+ return $obj;
+}
+
+sub new
+{
+ my $class = shift;
+ my $target = shift;
+ my $kind = shift;
+ my $self = bless { TARGET => $target, # parent target (left hand side)
+ KIND => $kind, # : or ::
+ DEPEND => [], DEPHASH => {}, # right hand args
+ COMMAND => [] # command(s)
+ },$class;
+ $self->depend(shift) if (@_);
+ $self->command(shift) if (@_);
+ return $self;
+}
+
+#
+# This code has to go somewhere but no good home obvious yet.
+# - only applies to ':' rules, but needs top level database
+# - perhaps in ->commands of derived ':' class?
+#
+sub find_commands
+{
+ my ($self) = @_;
+ if (! {$self->{COMMAND}} && @{$self->{DEPEND}})
+ {
+ my $info = $self->Info;
+ my $name = $self->Name;
+ my @dep = $self->depend;
+ my @rule = $info->patrule($self->Name);
+ if (@rule)
+ {
+ $self->depend($rule[0]);
+ $self->command($rule[1]);
+ }
+ }
+}
+
+#
+# Spew a shell script to perfom the 'make' e.g. make -n
+#
+sub Script
+{
+ my $self = shift;
+ return unless $self->out_of_date;
+ my @cmd = $self->exp_command;
+ if (@cmd)
+ {
+ my $file;
+ my $com = ($^O eq 'MSWin32') ? 'rem ': '# ';
+ print $com,$self->Name,"\n";
+ foreach $file ($self->exp_command)
+ {
+ $file =~ s/^[\ \s-]*//;
+ print "$file\n";
+ }
+ }
+}
+
+#
+# Normal 'make' method
+#
+sub Make
+{
+ my $self = shift;
+ my $file;
+ return unless ($self->out_of_date);
+ my @cmd = $self->exp_command;
+ my $info = $self->Info;
+ if (@cmd)
+ {
+ foreach my $file ($self->exp_command)
+ {
+ $file =~ s/^([\ \s-]*)//;
+ my $prefix = $1;
+ print "$file\n" unless ($prefix =~ /\@/);
+ my $code = $info->exec($file);
+ if ($code && $prefix !~ /-/)
+ {
+ die "Code $code from $file";
+ }
+ }
+ }
+}
+
+#
+# Print rule out in makefile syntax
+# - currently has variables expanded as debugging aid.
+# - will eventually become make -p
+# - may be useful for writing makefiles from MakeMaker too...
+#
+sub Print
+{
+ my $self = shift;
+ my $file;
+ print $self->Name,' ',$self->{KIND},' ';
+ foreach $file ($self->depend)
+ {
+ print " \\\n $file";
+ }
+ print "\n";
+ my @cmd = $self->exp_command;
+ if (@cmd)
+ {
+ foreach $file ($self->exp_command)
+ {
+ print "\t",$file,"\n";
+ }
+ }
+ else
+ {
+ print STDERR "No commands for ",$self->Name,"\n" unless ($self->target->phony);
+ }
+ print "\n";
+}
+
+package Make::Target;
+use Carp;
+use strict;
+use Cwd;
+
+#
+# Intermediate 'target' package
+# There is an instance of this for each 'target' that apears on
+# the left hand side of a rule i.e. for each thing that can be made.
+#
+sub new
+{
+ my ($class,$info,$target) = @_;
+ return bless { NAME => $target, # name of thing
+ MAKEFILE => $info, # Makefile context
+ Pass => 0 # Used to determine if 'done' this sweep
+ },$class;
+}
+
+sub date
+{
+ my $self = shift;
+ my $info = $self->Info;
+ return $info->date($self->Name);
+}
+
+sub phony
+{
+ my $self = shift;
+ return $self->Info->phony($self->Name);
+}
+
+
+sub colon
+{
+ my $self = shift;
+ if (@_)
+ {
+ if (exists $self->{COLON})
+ {
+ my $dep = $self->{COLON};
+ if (@_ == 1)
+ {
+ # merging an existing rule
+ my $other = shift;
+ $dep->depend(scalar $other->depend);
+ $dep->command(scalar $other->command);
+ }
+ else
+ {
+ $dep->depend(shift);
+ $dep->command(shift);
+ }
+ }
+ else
+ {
+ $self->{COLON} = (@_ == 1) ? shift->clone($self) : Make::Rule->new($self,':',@_);
+ }
+ }
+ if (exists $self->{COLON})
+ {
+ return (wantarray) ? ($self->{COLON}) : $self->{COLON};
+ }
+ else
+ {
+ return (wantarray) ? () : undef;
+ }
+}
+
+sub dcolon
+{
+ my $self = shift;
+ if (@_)
+ {
+ my $rule = (@_ == 1) ? shift->clone($self) : Make::Rule->new($self,'::',@_);
+ $self->{DCOLON} = [] unless (exists $self->{DCOLON});
+ push(@{$self->{DCOLON}},$rule);
+ }
+ return (exists $self->{DCOLON}) ? @{$self->{DCOLON}} : ();
+}
+
+sub Name
+{
+ return shift->{NAME};
+}
+
+sub Info
+{
+ return shift->{MAKEFILE};
+}
+
+sub ProcessColon
+{
+ my ($self) = @_;
+ my $c = $self->colon;
+ $c->find_commands if $c;
+}
+
+sub ExpandTarget
+{
+ my ($self) = @_;
+ my $target = $self->Name;
+ my $info = $self->Info;
+ my $colon = delete $self->{COLON};
+ my $dcolon = delete $self->{DCOLON};
+ foreach my $expand (split(/\s+/,$info->subsvars($target)))
+ {
+ next unless defined($expand);
+ my $t = $info->Target($expand);
+ if (defined $colon)
+ {
+ $t->colon($colon);
+ }
+ foreach my $d (@{$dcolon})
+ {
+ $t->dcolon($d);
+ }
+ }
+}
+
+sub done
+{
+ my $self = shift;
+ my $info = $self->Info;
+ my $pass = $info->pass;
+ return 1 if ($self->{Pass} == $pass);
+ $self->{Pass} = $pass;
+ return 0;
+}
+
+sub recurse
+{
+ my ($self,$method,@args) = @_;
+ my $info = $self->Info;
+ my $rule;
+ my $i = 0;
+ foreach $rule ($self->colon,$self->dcolon)
+ {
+ my $dep;
+ my $j = 0;
+ foreach $dep ($rule->exp_depend)
+ {
+ my $t = $info->{Depend}{$dep};
+ if (defined $t)
+ {
+ $t->$method(@args)
+ }
+ else
+ {
+ unless ($info->exists($dep))
+ {
+ my $dir = cwd();
+ die "Cannot recurse $method - no target $dep in $dir"
+ }
+ }
+ }
+ }
+}
+
+sub Script
+{
+ my $self = shift;
+ my $info = $self->Info;
+ my $rule = $self->colon;
+ return if ($self->done);
+ $self->recurse('Script');
+ foreach $rule ($self->colon,$self->dcolon)
+ {
+ $rule->Script;
+ }
+}
+
+sub Make
+{
+ my $self = shift;
+ my $info = $self->Info;
+ my $rule = $self->colon;
+ return if ($self->done);
+ $self->recurse('Make');
+ foreach $rule ($self->colon,$self->dcolon)
+ {
+ $rule->Make;
+ }
+}
+
+sub Print
+{
+ my $self = shift;
+ my $info = $self->Info;
+ return if ($self->done);
+ my $rule = $self->colon;
+ foreach $rule ($self->colon,$self->dcolon)
+ {
+ $rule->Print;
+ }
+ $self->recurse('Print');
+}
+
+package Make;
+use 5.005; # Need look-behind assertions
+use Carp;
+use strict;
+use Config;
+use Cwd;
+use File::Spec;
+use vars qw($VERSION);
+$VERSION = '1.00';
+
+my %date;
+
+sub phony
+{
+ my ($self,$name) = @_;
+ return exists $self->{PHONY}{$name};
+}
+
+sub suffixes
+{
+ my ($self) = @_;
+ return keys %{$self->{'SUFFIXES'}};
+}
+
+#
+# Construct a new 'target' (or find old one)
+# - used by parser to add to data structures
+#
+sub Target
+{
+ my ($self,$target) = @_;
+ unless (exists $self->{Depend}{$target})
+ {
+ my $t = Make::Target->new($self,$target);
+ $self->{Depend}{$target} = $t;
+ if ($target =~ /%/)
+ {
+ $self->{Pattern}{$target} = $t;
+ }
+ elsif ($target =~ /^\./)
+ {
+ $self->{Dot}{$target} = $t;
+ }
+ else
+ {
+ push(@{$self->{Targets}},$t);
+ }
+ }
+ return $self->{Depend}{$target};
+}
+
+#
+# Utility routine for patching %.o type 'patterns'
+#
+sub patmatch
+{
+ my $key = shift;
+ local $_ = shift;
+ my $pat = $key;
+ $pat =~ s/\./\\./;
+ $pat =~ s/%/(\[^\/\]*)/;
+ if (/$pat$/)
+ {
+ return $1;
+ }
+ return undef;
+}
+
+#
+# old vpath lookup routine
+#
+sub locate
+{
+ my $self = shift;
+ local $_ = shift;
+ return $_ if (-r $_);
+ my $key;
+ foreach $key (keys %{$self->{vpath}})
+ {
+ my $Pat;
+ if (defined($Pat = patmatch($key,$_)))
+ {
+ my $dir;
+ foreach $dir (split(/:/,$self->{vpath}{$key}))
+ {
+ return "$dir/$_" if (-r "$dir/$_");
+ }
+ }
+ }
+ return undef;
+}
+
+#
+# Convert traditional .c.o rules into GNU-like into %o : %c
+#
+sub dotrules
+{
+ my ($self) = @_;
+ my $t;
+ foreach $t (keys %{$self->{Dot}})
+ {
+ my $e = $self->subsvars($t);
+ $self->{Dot}{$e} = delete $self->{Dot}{$t} unless ($t eq $e);
+ }
+ my (@suffix) = $self->suffixes;
+ foreach $t (@suffix)
+ {
+ my $d;
+ my $r = delete $self->{Dot}{$t};
+ if (defined $r)
+ {
+ my @rule = ($r->colon) ? ($r->colon->depend) : ();
+ if (@rule)
+ {
+ delete $self->{Dot}{$t->Name};
+ print STDERR $t->Name," has dependants\n";
+ push(@{$self->{Targets}},$r);
+ }
+ else
+ {
+ # print STDERR "Build \% : \%$t\n";
+ $self->Target('%')->dcolon(['%'.$t],scalar $r->colon->command);
+ }
+ }
+ foreach $d (@suffix)
+ {
+ $r = delete $self->{Dot}{$t.$d};
+ if (defined $r)
+ {
+ # print STDERR "Build \%$d : \%$t\n";
+ $self->Target('%'.$d)->dcolon(['%'.$t],scalar $r->colon->command);
+ }
+ }
+ }
+ foreach $t (keys %{$self->{Dot}})
+ {
+ push(@{$self->{Targets}},delete $self->{Dot}{$t});
+ }
+}
+
+#
+# Return 'full' pathname of name given directory info.
+# - may be the place to do vpath stuff ?
+#
+
+my %pathname;
+
+sub pathname
+{
+ my ($self,$name) = @_;
+ my $hash = $self->{'Pathname'};
+ unless (exists $hash->{$name})
+ {
+ if (File::Spec->file_name_is_absolute($name))
+ {
+ $hash->{$name} = $name;
+ }
+ else
+ {
+ $name =~ s,^\./,,;
+ $hash->{$name} = File::Spec->catfile($self->{Dir},$name);
+ }
+ }
+ return $hash->{$name};
+
+}
+
+#
+# Return modified date of name if it exists
+#
+sub date
+{
+ my ($self,$name) = @_;
+ my $path = $self->pathname($name);
+ unless (exists $date{$path})
+ {
+ $date{$path} = -M $path;
+ }
+ return $date{$path};
+}
+
+#
+# Check to see if name is a target we can make or an existing
+# file - used to see if pattern rules are valid
+# - Needs extending to do vpath lookups
+#
+sub exists
+{
+ my ($self,$name) = @_;
+ return 1 if (exists $self->{Depend}{$name});
+ return 1 if defined $self->date($name);
+ # print STDERR "$name '$path' does not exist\n";
+ return 0;
+}
+
+#
+# See if we can find a %.o : %.c rule for target
+# .c.o rules are already converted to this form
+#
+sub patrule
+{
+ my ($self,$target) = @_;
+ my $key;
+ # print STDERR "Trying pattern for $target\n";
+ foreach $key (keys %{$self->{Pattern}})
+ {
+ my $Pat;
+ if (defined($Pat = patmatch($key,$target)))
+ {
+ my $t = $self->{Pattern}{$key};
+ my $rule;
+ foreach $rule ($t->dcolon)
+ {
+ my @dep = $rule->exp_depend;
+ if (@dep)
+ {
+ my $dep = $dep[0];
+ $dep =~ s/%/$Pat/g;
+ # print STDERR "Try $target : $dep\n";
+ if ($self->exists($dep))
+ {
+ foreach (@dep)
+ {
+ s/%/$Pat/g;
+ }
+ return (\ dep,scalar $rule->command);
+ }
+ }
+ }
+ }
+ }
+ return ();
+}
+
+#
+# Old code to handle vpath stuff - not used yet
+#
+sub needs
+{my ($self,$target) = @_;
+ unless ($self->{Done}{$target})
+ {
+ if (exists $self->{Depend}{$target})
+ {
+ my @depend = split(/\s+/,$self->subsvars($self->{Depend}{$target}));
+ foreach (@depend)
+ {
+ $self->needs($_);
+ }
+ }
+ else
+ {
+ my $vtarget = $self->locate($target);
+ if (defined $vtarget)
+ {
+ $self->{Need}{$vtarget} = $target;
+ }
+ else
+ {
+ $self->{Need}{$target} = $target;
+ }
+ }
+ }
+}
+
+#
+# Substitute $(xxxx) and $x style variable references
+# - should handle ${xxx} as well
+# - recurses till they all go rather than doing one level,
+# which may need fixing
+#
+sub subsvars
+{
+ my $self = shift;
+ local $_ = shift;
+ my @var = @_;
+ push(@var,$self->{Override},$self->{Vars},\%ENV);
+ croak("Trying to subsitute undef value") unless (defined $_);
+ while (/(?<!\$)\$\(([^()]+)\)/ || /(?<!\$)\$([<\ ^?*])/)
+ {
+ my ($key,$head,$tail) = ($1,$`,$');
+ my $value;
+ if ($key =~ /^([\w._]+|\S)(?::(.*))?$/)
+ {
+ my ($var,$op) = ($1,$2);
+ foreach my $hash (@var)
+ {
+ $value = $hash->{$var};
+ if (defined $value)
+ {
+ last;
+ }
+ }
+ unless (defined $value)
+ {
+ die "$var not defined in '$_'" unless (length($var) > 1);
+ $value = '';
+ }
+ if (defined $op)
+ {
+ if ($op =~ /^s(.).*\1.*\1/)
+ {
+ local $_ = $self->subsvars($value);
+ $op =~ s/\\/\\\\/g;
+ eval $op.'g';
+ $value = $_;
+ }
+ else
+ {
+ die "$var:$op = '$value'\n";
+ }
+ }
+ }
+ elsif ($key =~ /wildcard\s*(.*)$/)
+ {
+ $value = join(' ',glob($self->pathname($1)));
+ }
+ elsif ($key =~ /shell\s*(.*)$/)
+ {
+ $value = join(' ',split('\n',`$1`));
+ }
+ elsif ($key =~ /addprefix\s*([^,]*),(.*)$/)
+ {
+ $value = join(' ',map($1 . $_,split('\s+',$2)));
+ }
+ elsif ($key =~ /notdir\s*(.*)$/)
+ {
+ my @files = split(/\s+/,$1);
+ foreach (@files)
+ {
+ s#^.*/([^/]*)$#$1#;
+ }
+ $value = join(' ',@files);
+ }
+ elsif ($key =~ /dir\s*(.*)$/)
+ {
+ my @files = split(/\s+/,$1);
+ foreach (@files)
+ {
+ s#^(.*)/[^/]*$#$1#;
+ }
+ $value = join(' ',@files);
+ }
+ elsif ($key =~ /^subst\s+([^,]*),([^,]*),(.*)$/)
+ {
+ my ($a,$b) = ($1,$2);
+ $value = $3;
+ $a =~ s/\./\\./;
+ $value =~ s/$a/$b/;
+ }
+ elsif ($key =~ /^mktmp,(\S+)\s*(.*)$/)
+ {
+ my ($file,$content) = ($1,$2);
+ open(TMP,">$file") || die "Cannot open $file:$!";
+ $content =~ s/\\n//g;
+ print TMP $content;
+ close(TMP);
+ $value = $file;
+ }
+ else
+ {
+ warn "Cannot evaluate '$key' in '$_'\n";
+ }
+ $_ = "$head$value$tail";
+ }
+ s/\$\$/\$/g;
+ return $_;
+}
+
+#
+# Split a string into tokens - like split(/\s+/,...) but handling
+# $(keyword ...) with embedded \s
+# Perhaps should also understand "..." and '...' ?
+#
+sub tokenize
+{
+ local $_ = $_[0];
+ my @result = ();
+ s/\s+$//;
+ while (length($_))
+ {
+ s/^\s+//;
+ last unless (/^\S/);
+ my $token = "";
+ while (/^\S/)
+ {
+ if (s/^\$([\(\{])//)
+ {
+ $token .= $&;
+ my $paren = $1 eq '(';
+ my $brace = $1 eq '{';
+ my $count = 1;
+ while (length($_) && ($paren || $brace))
+ {
+ s/^.//;
+ $token .= $&;
+ $paren += ($& eq '(');
+ $paren -= ($& eq ')');
+ $brace += ($& eq '{');
+ $brace -= ($& eq '}');
+ }
+ die "Mismatched {} in $_[0]" if ($brace);
+ die "Mismatched () in $_[0]" if ($paren);
+ }
+ elsif (s/^(\$\S?|[^\s\$]+)//)
+ {
+ $token .= $&;
+ }
+ }
+ push(@result,$token);
+ }
+ return (wantarray) ? @result : \ result;
+}
+
+
+#
+# read makefile (or fragment of one) either as a result
+# of a command line, or an 'include' in another makefile.
+#
+sub makefile
+{
+ my ($self,$makefile,$name) = @_;
+ local $_;
+# print STDERR "Reading $name\n";
+Makefile:
+ while (<$makefile>)
+ {
+ last unless (defined $_);
+ chomp($_);
+ if (/\\$/)
+ {
+ chop($_);
+ s/\s*$//;
+ my $more = <$makefile>;
+ $more =~ s/^\s*/ /;
+ $_ .= $more;
+ redo;
+ }
+ next if (/^\s*#/);
+ next if (/^\s*$/);
+ s/#.*$//;
+ s/^\s+//;
+ if (/^(-?)include\s+(.*)$/)
+ {
+ my $opt = $1;
+ my $file;
+ foreach $file (tokenize($self->subsvars($2)))
+ {
+ local *Makefile;
+ my $path = $self->pathname($file);
+ if (open(Makefile,"<$path"))
+ {
+ $self->makefile(\*Makefile,$path);
+ close(Makefile);
+ }
+ else
+ {
+ warn "Cannot open $path:$!" unless ($opt eq '-') ;
+ }
+ }
+ }
+ elsif (/^\s*([\w._]+)\s*:?=\s*(.*)$/)
+ {
+ $self->{Vars}{$1} = (defined $2) ? $2 : "";
+# print STDERR "$1 = ",$self->{Vars}{$1},"\n";
+ }
+ elsif (/^vpath\s+(\S+)\s+(.*)$/)
+ {my ($pat,$path) = ($1,$2);
+ $self->{Vpath}{$pat} = $path;
+ }
+ elsif (/^\s*([^:]*)(::?)\s*(.*)$/)
+ {
+ my ($target,$kind,$depend) = ($1,$2,$3);
+ my @cmnds;
+ if ($depend =~ /^([^;]*);(.*)$/)
+ {
+ ($depend,$cmnds[0]) = ($1,$2);
+ }
+ while (<$makefile>)
+ {
+ next if (/^\s*#/);
+ next if (/^\s*$/);
+ last unless (/^\t/);
+ chop($_);
+ if (/\\$/)
+ {
+ chop($_);
+ $_ .= ' ';
+ $_ .= <$makefile>;
+ redo;
+ }
+ next if (/^\s*$/);
+ s/^\s+//;
+ push(@cmnds,$_);
+ }
+ $depend =~ s/\s\s+/ /;
+ $target =~ s/\s\s+/ /;
+ my @depend = tokenize($depend);
+ foreach (tokenize($target))
+ {
+ my $t = $self->Target($_);
+ my $index = 0;
+ if ($kind eq '::' || /%/)
+ {
+ $t->dcolon(\ depend,\ cmnds);
+ }
+ else
+ {
+ $t->colon(\ depend,\ cmnds);
+ }
+ }
+ redo Makefile;
+ }
+ else
+ {
+ warn "Ignore '$_'\n";
+ }
+ }
+}
+
+sub pseudos
+{
+ my $self = shift;
+ my $key;
+ foreach $key (qw(SUFFIXES PHONY PRECIOUS PARALLEL))
+ {
+ my $t = delete $self->{Dot}{'.'.$key};
+ if (defined $t)
+ {
+ my $dep;
+ $self->{$key} = {};
+ foreach $dep ($t->colon->exp_depend)
+ {
+ $self->{$key}{$dep} = 1;
+ }
+ }
+ }
+}
+
+
+sub ExpandTarget
+{
+ my $self = shift;
+ foreach my $t (@{$self->{'Targets'}})
+ {
+ $t->ExpandTarget;
+ }
+ foreach my $t (@{$self->{'Targets'}})
+ {
+ $t->ProcessColon;
+ }
+}
+
+sub parse
+{
+ my ($self,$file) = @_;
+ if (defined $file)
+ {
+ $file = $self->pathname($file);
+ }
+ else
+ {
+ my @files = qw(makefile Makefile);
+ unshift(@files,'GNUmakefile') if ($self->{GNU});
+ my $name;
+ foreach $name (@files)
+ {
+ $file = $self->pathname($name);
+ if (-r $file)
+ {
+ $self->{Makefile} = $name;
+ last;
+ }
+ }
+ }
+ local (*Makefile);
+ open(Makefile,"<$file") || croak("Cannot open $file:$!");
+ $self->makefile(\*Makefile,$file);
+ close(Makefile);
+
+ # Next bits should really be done 'lazy' on need.
+
+ $self->pseudos; # Pull out .SUFFIXES etc.
+ $self->dotrules; # Convert .c.o into %.o : %.c
+}
+
+sub PrintVars
+{
+ my $self = shift;
+ local $_;
+ foreach (keys %{$self->{Vars}})
+ {
+ print "$_ = ",$self->{Vars}{$_},"\n";
+ }
+ print "\n";
+}
+
+sub exec
+{
+ my $self = shift;
+ undef %date;
+ $generation++;
+ if ($^O eq 'MSWin32')
+ {
+ my $cwd = cwd();
+ my $ret;
+ chdir $self->{Dir};
+ $ret = system(@_);
+ chdir $cwd;
+ return $ret;
+ }
+ else
+ {
+ my $pid = fork;
+ if ($pid)
+ {
+ waitpid $pid,0;
+ return $?;
+ }
+ else
+ {
+ my $dir = $self->{Dir};
+ chdir($dir) || die "Cannot cd to $dir";
+ # handle leading VAR=value here ?
+ # To handle trivial cases like ': libpTk.a' force using /bin/sh
+ exec("/bin/sh","-c",@_) || confess "Cannot exec ".join(' ',@_);
+ }
+ }
+}
+
+sub NextPass { shift->{Pass}++ }
+sub pass { shift->{Pass} }
+
+sub apply
+{
+ my $self = shift;
+ my $method = shift;
+ $self->NextPass;
+ my @targets = ();
+ # print STDERR join(' ',Apply => $method,@_),"\n";
+ foreach (@_)
+ {
+ if (/^(\w+)=(.*)$/)
+ {
+ # print STDERR "OVERRIDE: $1 = $2\n";
+ $self->{Override}{$1} = $2;
+ }
+ else
+ {
+ push(@targets,$_);
+ }
+ }
+ #
+ # This expansion is dubious as it alters the database
+ # as a function of current values of Override.
+ #
+ $self->ExpandTarget; # Process $(VAR) :
+ @targets = ($self->{'Targets'}[0])->Name unless (@targets);
+ # print STDERR join(' ',Targets => $method,map($_->Name,@targets)),"\n";
+ foreach (@targets)
+ {
+ my $t = $self->{Depend}{$_};
+ unless (defined $t)
+ {
+ print STDERR join(' ',$method,@_),"\n";
+ die "Cannot `$method' - no target $_"
+ }
+ $t->$method();
+ }
+}
+
+sub Script
+{
+ shift->apply(Script => @_);
+}
+
+sub Print
+{
+ shift->apply(Print => @_);
+}
+
+sub Make
+{
+ shift->apply(Make => @_);
+}
+
+sub new
+{
+ my ($class,%args) = @_;
+ unless (defined $args{Dir})
+ {
+ chomp($args{Dir} = getcwd());
+ }
+ my $self = bless { %args,
+ Pattern => {}, # GNU style %.o : %.c
+ Dot => {}, # Trad style .c.o
+ Vpath => {}, # vpath %.c info
+ Vars => {}, # Variables defined in makefile
+ Depend => {}, # hash of targets
+ Targets => [], # ordered version so we can find 1st one
+ Pass => 0, # incremented each sweep
+ Pathname => {}, # cache of expanded names
+ Need => {},
+ Done => {},
+ },$class;
+ $self->{Vars}{CC} = $Config{cc};
+ $self->{Vars}{AR} = $Config{ar};
+ $self->{Vars}{CFLAGS} = $Config{optimize};
+ $self->makefile(\*DATA,__FILE__);
+ $self->parse($self->{Makefile});
+ return $self;
+}
+
+=head1 NAME
+
+Make - module for processing makefiles
+
+=head1 SYNOPSIS
+
+ require Make;
+ my $make = Make->new(...);
+ $make->parse($file);
+ $make->Script(@ARGV)
+ $make->Make(@ARGV)
+ $make->Print(@ARGV)
+
+ my $targ = $make->Target($name);
+ $targ->colon([dependancy...],[command...]);
+ $targ->dolon([dependancy...],[command...]);
+ my @depends = $targ->colon->depend;
+ my @commands = $targ->colon->command;
+
+=head1 DESCRIPTION
+
+Make->new creates an object if C<new(Makefile =E<gt> $file)> is specified
+then it is parsed. If not the usual makefile Makefile sequence is
+used. (If GNU => 1 is passed to new then GNUmakefile is looked for first.)
+
+C<$make-E<gt>Make(target...)> 'makes' the target(s) specified
+(or the first 'real' target in the makefile).
+
+C<$make-E<gt>Print> can be used to 'print' to current C<select>'ed stream
+a form of the makefile with all variables expanded.
+
+C<$make-E<gt>Script(target...)> can be used to 'print' to
+current C<select>'ed stream the equivalent bourne shell script
+that a make would perform i.e. the output of C<make -n>.
+
+There are other methods (used by parse) which can be used to add and
+manipulate targets and their dependants. There is a hierarchy of classes
+which is still evolving. These classes and their methods will be documented when
+they are a little more stable.
+
+The syntax of makefile accepted is reasonably generic, but I have not re-read
+any documentation yet, rather I have implemented my own mental model of how
+make works (then fixed it...).
+
+In addition to traditional
+
+ .c.o :
+ $(CC) -c ...
+
+GNU make's 'pattern' rules e.g.
+
+ %.o : %.c
+ $(CC) -c ...
+
+Likewise a subset of GNU makes $(function arg...) syntax is supported.
+
+Via pmake Make has built perl/Tk from the C<MakeMaker> generated Makefiles...
+
+=head1 BUGS
+
+At present C<new> must always find a makefile, and
+C<$make-E<gt>parse($file)> can only be used to augment that file.
+
+More attention needs to be given to using the package to I<write> makefiles.
+
+The rules for matching 'dot rules' e.g. .c.o and/or pattern rules e.g. %.o : %.c
+are suspect. For example give a choice of .xs.o vs .xs.c + .c.o behaviour
+seems a little odd.
+
+Variables are probably substituted in different 'phases' of the process
+than in make(1) (or even GNU make), so 'clever' uses will probably not
+work.
+
+UNIXisms abound.
+
+=head1 SEE ALSO
+
+L<pmake>
+
+=head1 AUTHOR
+
+Nick Ing-Simmons
+
+=cut
+
+1;
+#
+# Remainder of file is in makefile syntax and constitutes
+# the built in rules
+#
+__DATA__
+
+.SUFFIXES: .o .c .y .h .sh .cps
+
+.c.o :
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
+
+.c :
+ $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $< $(LDFLAGS) $(LDLIBS)
+
+.y.o:
+ $(YACC) $<
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ y.tab.c
+ $(RM) y.tab.c
+
+.y.c:
+ $(YACC) $<
+ mv y.tab.c $@
+
+
Added: trunk/plugins/gbf-mkfile/GBF/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/GBF/Makefile.am Wed Dec 31 11:55:52 2008
@@ -0,0 +1,5 @@
+perlmodulesdir = $(pkgdatadir)/GBF
+perlmodules_DATA = Make.pm General.pm
+
+EXTRA_DIST = $(perlmodules_DATA)
+
Added: trunk/plugins/gbf-mkfile/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/Makefile.am Wed Dec 31 11:55:52 2008
@@ -0,0 +1,58 @@
+# Plugin UI file
+plugin_uidir = $(anjuta_ui_dir)
+plugin_ui_DATA =
+
+# Plugin glade file
+plugin_gladedir = $(anjuta_glade_dir)
+plugin_glade_DATA =
+
+# Plugin icon file
+plugin_pixmapsdir = $(anjuta_image_dir)
+plugin_pixmaps_DATA = gbf-mkfile-plugin-48.png
+
+# Plugin scripts
+scriptsdir = $(bindir)
+scripts_SCRIPTS = gbf-mkfile-parse
+
+# Plugin description file
+plugin_in_files = gbf-mkfile.plugin.in
+%.plugin: %.plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugindir = $(anjuta_plugin_dir)
+plugin_DATA = $(plugin_in_files:.plugin.in=.plugin)
+
+AM_CPPFLAGS = \
+ $(WARN_CFLAGS) \
+ $(DEPRECATED_FLAGS) \
+ $(LIBANJUTA_CFLAGS) \
+ -DSCRIPTS_DIR=\"$(scriptsdir)\"
+
+plugin_LTLIBRARIES = \
+ libgbf-mkfile.la
+
+libgbf_mkfile_la_SOURCES = \
+ plugin.c \
+ gbf-mkfile-project.c \
+ gbf-mkfile-project.h \
+ gbf-mkfile-config.c \
+ gbf-mkfile-config.h \
+ gbf-mkfile-properties.c \
+ gbf-mkfile-properties.h
+
+libgbf_mkfile_la_LDFLAGS = $(ANJUTA_PLUGIN_LDFLAGS)
+
+libgbf_mkfile_la_LIBADD = \
+ $(LIBANJUTA_LIBS)
+
+EXTRA_DIST = \
+ $(plugin_in_files) \
+ $(plugin_DATA) \
+ $(plugin_ui_DATA) \
+ $(plugin_pixmaps_DATA)
+
+DISTCLEANFILES = \
+ $(plugin_DATA) \
+ $(plugin_in_files)
+
+SUBDIRS = GBF
+
Added: trunk/plugins/gbf-mkfile/gbf-mkfile-config.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-config.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,351 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-mkfile-config.c
+ *
+ * This file is part of the Gnome Build framework
+ * Copyright (C) 2005 Eric Greveson
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ * JP Rosevear
+ * Dave Camp
+ * Naba Kumar
+ * Gustavo Giráez
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include "gbf-mkfile-config.h"
+
+typedef struct _GbfMkfileConfigEntry GbfMkfileConfigEntry;
+
+struct _GbfMkfileConfigEntry {
+ gchar *key;
+ GbfMkfileConfigValue *value;
+};
+
+struct _GbfMkfileConfigMapping {
+ GList *pairs;
+};
+
+
+GbfMkfileConfigValue *
+gbf_mkfile_config_value_new (GbfMkfileValueType type)
+{
+ GbfMkfileConfigValue *new_value;
+
+ g_return_val_if_fail (type != GBF_MKFILE_TYPE_INVALID, NULL);
+
+ new_value = g_new0 (GbfMkfileConfigValue, 1);
+ new_value->type = type;
+
+ switch (type) {
+ case GBF_MKFILE_TYPE_STRING:
+ new_value->string = NULL;
+ break;
+ case GBF_MKFILE_TYPE_MAPPING:
+ new_value->mapping = gbf_mkfile_config_mapping_new ();
+ break;
+ case GBF_MKFILE_TYPE_LIST:
+ new_value->list = NULL;
+ break;
+ default:
+ break;
+ }
+
+ return new_value;
+}
+
+void
+gbf_mkfile_config_value_free (GbfMkfileConfigValue *value)
+{
+ if (value == NULL)
+ return;
+
+ switch (value->type) {
+ case GBF_MKFILE_TYPE_STRING:
+ g_free (value->string);
+ value->string = NULL;
+ break;
+ case GBF_MKFILE_TYPE_MAPPING:
+ gbf_mkfile_config_mapping_destroy (value->mapping);
+ value->mapping = NULL;
+ break;
+ case GBF_MKFILE_TYPE_LIST:
+ if (value->list) {
+ g_slist_foreach (value->list,
+ (GFunc) gbf_mkfile_config_value_free,
+ NULL);
+ g_slist_free (value->list);
+ value->list = NULL;
+ }
+ break;
+ default:
+ g_warning ("%s", _("Invalid GbfMkfileConfigValue type"));
+ break;
+ }
+ g_free (value);
+}
+
+GbfMkfileConfigValue *
+gbf_mkfile_config_value_copy (const GbfMkfileConfigValue *source)
+{
+ GbfMkfileConfigValue *value;
+ GSList *l;
+
+ if (source == NULL)
+ return NULL;
+
+ value = gbf_mkfile_config_value_new (source->type);
+
+ switch (source->type) {
+ case GBF_MKFILE_TYPE_STRING:
+ value->string = g_strdup (source->string);
+ break;
+ case GBF_MKFILE_TYPE_MAPPING:
+ value->mapping = gbf_mkfile_config_mapping_copy (source->mapping);
+ break;
+ case GBF_MKFILE_TYPE_LIST:
+ value->list = NULL;
+ for (l = source->list; l; l = l->next) {
+ GbfMkfileConfigValue *new_value =
+ gbf_mkfile_config_value_copy ((GbfMkfileConfigValue *)l->data);
+ value->list = g_slist_prepend (value->list, new_value);
+ }
+ value->list = g_slist_reverse (value->list);
+ break;
+ default:
+ g_warning ("%s", _("Invalid GbfMkfileConfigValue type"));
+ break;
+ }
+
+ return value;
+}
+
+void
+gbf_mkfile_config_value_set_string (GbfMkfileConfigValue *value,
+ const gchar *string)
+{
+ g_return_if_fail (value != NULL && value->type == GBF_MKFILE_TYPE_STRING);
+
+ if (value->string)
+ g_free (value->string);
+
+ value->string = g_strdup (string);
+}
+
+void
+gbf_mkfile_config_value_set_list (GbfMkfileConfigValue *value,
+ GSList *list)
+{
+ GSList *l;
+
+ g_return_if_fail (value != NULL && value->type == GBF_MKFILE_TYPE_LIST);
+
+ if (value->list) {
+ g_slist_foreach (value->list, (GFunc) gbf_mkfile_config_value_free, NULL);
+ g_slist_free (value->list);
+ }
+
+ value->list = NULL;
+ for (l = list; l; l = l->next) {
+ GbfMkfileConfigValue *new_value = gbf_mkfile_config_value_copy
+ ((GbfMkfileConfigValue *)l->data);
+ value->list = g_slist_prepend (value->list, new_value);
+ }
+
+ value->list = g_slist_reverse (value->list);
+}
+
+void
+gbf_mkfile_config_value_set_list_nocopy (GbfMkfileConfigValue *value,
+ GSList *list)
+{
+ g_return_if_fail (value != NULL && value->type == GBF_MKFILE_TYPE_LIST);
+
+ if (value->list) {
+ g_slist_foreach (value->list, (GFunc) gbf_mkfile_config_value_free, NULL);
+ g_slist_free (value->list);
+ }
+ value->list = list;
+}
+
+void
+gbf_mkfile_config_value_set_mapping (GbfMkfileConfigValue *value,
+ GbfMkfileConfigMapping *mapping)
+{
+ g_return_if_fail (value != NULL && value->type == GBF_MKFILE_TYPE_MAPPING);
+
+ gbf_mkfile_config_mapping_destroy (value->mapping);
+
+ value->mapping = mapping;
+}
+
+GbfMkfileConfigMapping *
+gbf_mkfile_config_mapping_new (void)
+{
+ GbfMkfileConfigMapping *new_map;
+
+ new_map = g_new0 (GbfMkfileConfigMapping, 1);
+ new_map->pairs = NULL;
+
+ return new_map;
+}
+
+void
+gbf_mkfile_config_mapping_destroy (GbfMkfileConfigMapping *mapping)
+{
+ GbfMkfileConfigEntry *entry;
+ GList *lp;
+
+ if (mapping == NULL)
+ return;
+
+ for (lp = mapping->pairs; lp; lp = lp->next) {
+ entry = (GbfMkfileConfigEntry *) lp->data;
+ gbf_mkfile_config_value_free (entry->value);
+ g_free (entry->key);
+ g_free (entry);
+ }
+ g_list_free (mapping->pairs);
+ g_free (mapping);
+}
+
+GbfMkfileConfigMapping *
+gbf_mkfile_config_mapping_copy (const GbfMkfileConfigMapping *source)
+{
+ GbfMkfileConfigMapping *new_map;
+ GList *lp;
+
+ if (source == NULL)
+ return NULL;
+
+ new_map = g_new0 (GbfMkfileConfigMapping, 1);
+ new_map->pairs = NULL;
+
+ for (lp = source->pairs; lp; lp = lp->next) {
+ GbfMkfileConfigEntry *new_entry, *entry;
+
+ entry = (GbfMkfileConfigEntry *) lp->data;
+ if (entry == NULL)
+ continue;
+
+ new_entry = g_new0 (GbfMkfileConfigEntry, 1);
+ new_entry->key = g_strdup (entry->key);
+ new_entry->value = gbf_mkfile_config_value_copy (entry->value);
+ new_map->pairs = g_list_prepend (new_map->pairs, new_entry);
+ }
+
+ return new_map;
+}
+
+GbfMkfileConfigValue *
+gbf_mkfile_config_mapping_lookup (GbfMkfileConfigMapping *mapping,
+ const gchar *key)
+{
+ GbfMkfileConfigEntry *entry = NULL;
+ GList *lp;
+
+ g_return_val_if_fail (mapping != NULL && key != NULL, NULL);
+
+ for (lp = mapping->pairs; lp; lp = lp->next) {
+ entry = (GbfMkfileConfigEntry *) lp->data;
+ if (!strcmp (entry->key, key))
+ break;
+ }
+
+ return (lp ? entry->value : NULL);
+}
+
+gboolean
+gbf_mkfile_config_mapping_insert (GbfMkfileConfigMapping *mapping,
+ const gchar *key,
+ GbfMkfileConfigValue *value)
+{
+ GbfMkfileConfigEntry *entry = NULL;
+ GList *lp;
+ gboolean insert = TRUE;
+
+ g_return_val_if_fail (mapping != NULL && key != NULL, FALSE);
+
+ for (lp = mapping->pairs; lp; lp = lp->next) {
+ entry = (GbfMkfileConfigEntry *) lp->data;
+ if (!strcmp (entry->key, key)) {
+ insert = FALSE;
+ break;
+ }
+ }
+
+ if (insert) {
+ GbfMkfileConfigEntry *new_entry;
+
+ new_entry = g_new0 (GbfMkfileConfigEntry, 1);
+ new_entry->key = g_strdup (key);
+ new_entry->value = value;
+ mapping->pairs = g_list_prepend (mapping->pairs, new_entry);
+ }
+
+ return insert;
+}
+
+gboolean
+gbf_mkfile_config_mapping_remove (GbfMkfileConfigMapping *mapping,
+ const gchar *key)
+{
+ GbfMkfileConfigEntry *entry = NULL;
+ GList *lp;
+ gboolean remove = FALSE;
+
+ g_return_val_if_fail (mapping != NULL && key != NULL, FALSE);
+
+ for (lp = mapping->pairs; lp; lp = lp->next) {
+ entry = (GbfMkfileConfigEntry *) lp->data;
+ if (!strcmp (entry->key, key)) {
+ remove = TRUE;
+ break;
+ }
+ }
+
+ if (remove) {
+ gbf_mkfile_config_value_free (entry->value);
+ g_free (entry->key);
+ g_free (entry);
+ mapping->pairs = g_list_delete_link (mapping->pairs, lp);
+ }
+
+ return remove;
+}
+
+void
+gbf_mkfile_config_mapping_foreach (GbfMkfileConfigMapping *mapping,
+ GbfMkfileConfigMappingFunc callback,
+ gpointer user_data)
+{
+ GbfMkfileConfigEntry *entry = NULL;
+ GList *lp;
+
+ g_return_if_fail (mapping != NULL && callback != NULL);
+
+ for (lp = mapping->pairs; lp; lp = lp->next) {
+ entry = (GbfMkfileConfigEntry *) lp->data;
+ callback (entry->key, entry->value, user_data);
+ }
+}
Added: trunk/plugins/gbf-mkfile/gbf-mkfile-config.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-config.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,89 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-mkfile-config.h
+ *
+ * This file is part of the Gnome Build framework
+ * Copyright (C) 2005 Eric Greveson
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ * JP Rosevear
+ * Dave Camp
+ * Naba Kumar
+ * Gustavo Giráez
+ */
+
+#ifndef __GBF_MKFILE_CONFIG_H__
+#define __GBF_MKFILE_CONFIG_H__
+
+#include <glib.h>
+
+/* config data structures */
+typedef enum {
+ GBF_MKFILE_TYPE_INVALID,
+ GBF_MKFILE_TYPE_STRING,
+ GBF_MKFILE_TYPE_MAPPING,
+ GBF_MKFILE_TYPE_LIST
+} GbfMkfileValueType;
+
+typedef struct _GbfMkfileConfigValue GbfMkfileConfigValue;
+typedef struct _GbfMkfileConfigMapping GbfMkfileConfigMapping;
+
+struct _GbfMkfileConfigValue {
+ GbfMkfileValueType type;
+ gchar *string;
+ GbfMkfileConfigMapping *mapping;
+ GSList *list;
+};
+
+typedef void (*GbfMkfileConfigMappingFunc) (const gchar *key,
+ GbfMkfileConfigValue *value,
+ gpointer user_data);
+/* ---------- public interface */
+
+GbfMkfileConfigValue *gbf_mkfile_config_value_new (GbfMkfileValueType type);
+void gbf_mkfile_config_value_free (GbfMkfileConfigValue *value);
+GbfMkfileConfigValue *gbf_mkfile_config_value_copy (const GbfMkfileConfigValue *source);
+
+void gbf_mkfile_config_value_set_string (GbfMkfileConfigValue *value,
+ const gchar *string);
+void gbf_mkfile_config_value_set_list (GbfMkfileConfigValue *value,
+ GSList *list);
+void gbf_mkfile_config_value_set_list_nocopy (GbfMkfileConfigValue *value,
+ GSList *list);
+void gbf_mkfile_config_value_set_mapping (GbfMkfileConfigValue *value,
+ GbfMkfileConfigMapping *mapping);
+
+#define gbf_mkfile_config_value_get_string(x) ((const gchar *)(((GbfMkfileConfigValue *)x)->string))
+#define gbf_mkfile_config_value_get_list(x) ((GSList *)(((GbfMkfileConfigValue *)x)->list))
+#define gbf_mkfile_config_value_get_mapping(x) ((GbfMkfileConfigMapping *)(((GbfMkfileConfigValue *)x)->mapping))
+
+GbfMkfileConfigMapping *gbf_mkfile_config_mapping_new (void);
+void gbf_mkfile_config_mapping_destroy (GbfMkfileConfigMapping *mapping);
+GbfMkfileConfigMapping *gbf_mkfile_config_mapping_copy (const GbfMkfileConfigMapping *source);
+GbfMkfileConfigValue *gbf_mkfile_config_mapping_lookup (GbfMkfileConfigMapping *mapping,
+ const gchar *key);
+gboolean gbf_mkfile_config_mapping_insert (GbfMkfileConfigMapping *mapping,
+ const gchar *key,
+ GbfMkfileConfigValue *value);
+gboolean gbf_mkfile_config_mapping_remove (GbfMkfileConfigMapping *mapping,
+ const gchar *key);
+void gbf_mkfile_config_mapping_foreach (GbfMkfileConfigMapping *mapping,
+ GbfMkfileConfigMappingFunc callback,
+ gpointer user_data);
+
+#endif /* __GBF_MKFILE_CONFIG_H__ */
Added: trunk/plugins/gbf-mkfile/gbf-mkfile-parse.in
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-parse.in Wed Dec 31 11:55:52 2008
@@ -0,0 +1,1797 @@
+#!/usr/bin/perl
+# -*- perl -*-
+
+eval 'exec @PERL@ -S $0 ${1+"$@"}'
+ if 0;
+
+BEGIN
+{
+ my $prefix = "@prefix@";
+ my $perlmodulesdir = "@datadir@/@PACKAGE@";
+ unshift @INC, "$perlmodulesdir";
+}
+
+use strict;
+
+use GBF::General;
+use GBF::Make;
+
+# I18n
+use Locale::gettext;
+use POSIX;
+
+setlocale(LC_MESSAGES, "");
+textdomain("@PACKAGE@");
+
+my $debug = 0;
+my $verbose = 0;
+my $dry_run = 0;
+
+
+sub debug { my $message = $_[0]; print STDERR "DEBUG: $message\n" if $debug; }
+
+# File taken from Automake GBF backend and modified for Makefile use by Eric
+# Greveson. Uses Make.pm from Nick Ing-Simmons' Make-1.00 CPAN module.
+
+# General FIXMEs:
+
+# - implement help for the command line
+
+# - Don't remove backslashes from read macros
+
+# File index:
+# 1. CONSTANTS AND VARIABLES
+# 3. SOURCE EXTRACTION
+# 4. GROUP AND PROJECT CONSTRUCTION
+# 5. PROJECT MODIFICATION
+# 6. TARGET WRITER FUNCTIONS
+# 7. XML PRINTING FUNCTIONS
+# 8. XML SCANNING
+# 9. XML PROCESSING
+# 10. HELPER FUNCTIONS
+# 11. MAIN PROGRAM
+
+##########################################################################################
+#
+# BEGIN DOCUMENTATION
+#
+# Syntax:
+# ---------------------------------------------------------------------------------------
+#
+# gbf-mkfile-parse [options] <operation> <argument>
+#
+# Operations:
+#
+# --get : argument is the project root
+# analyzes the Makefile project and outputs an xml representation
+#
+# --set : argument is a file (or - for stdin)
+# reads an xml file which specifies operations to be performed
+# outputs changed project groups affected by the operations
+#
+# Options:
+#
+# -v (--verbose) : output additional information during processing
+# -n (--dry-run) : when in --set operation mode don't alter any files, just
+# parse the input
+# -d (--debug) : output debugging information
+#
+# Output:
+#
+# In stdout the script outputs the xml representation of the project
+# (or the changed groups). Through stderr the script outputs errors and warnings
+# using the following format:
+#
+# DEBUG: message
+# ERROR(code): message
+# WARNING(code): message
+#
+# Error codes:
+#
+# 0 Success
+# 1-99 General errors
+# 1: Malformed project
+# 2: Invalid directory
+# 3: Cannot open output file
+# 4: Cannot open input file
+# 5: Syntax error
+# 100-199 Makefile parser error
+# 100: Can't open file
+# 101: Invalid trailing backslash
+# 200-299 Extraction errors
+# 200: Invalid primary prefix
+# 300-399 Target writer errors
+# 300: Invalid operation
+# 301: Invalid operand
+# 302: Unimplemented target writer
+# 303: Group not found
+# 304: Can't write file
+# 305: Target/group already exists
+#
+# Warning codes:
+#
+# 0 Success
+# 1-99 General warnings
+# 100-199 Makefile parser warning
+# 100: Adding text to not previously declared macro
+# 102: Makefile for a subdirectory not found
+# 200-299 Extraction warning
+# 200: General semantic error
+# 300-399 Target writer warning
+# 300: Creating an already existing macro
+# 301: Out of bounds line number
+# 302: Target types don't match while creating a new simple primary target
+#
+#
+#
+# Output format (see output.dtd):
+# ---------------------------------------------------------------------------------------
+#
+# Sample output:
+# <?xml version='1.0' encoding='ISO-8859-1' standalone='yes'?>
+# <!DOCTYPE project []>
+# <project root="/home/gustavo/tmp/gdl">
+#
+# the 'root' attribute is a full path to the root of the project
+#
+# <config>
+# <param name="source_generators">
+# <item name="glib_genmarshal" value="GLIB_GENMARSHAL"/>
+# <item name="glib_mkenums" value="GLIB_MKENUMS"/>
+# <item name="orbit_idl" value="ORBIT_IDL"/>
+# </param>
+# </config>
+#
+# the project itself can contain some 'config' parameters
+# the config is composed of 'param' elements
+# special characters in parameter values are escaped
+# \ (the backslash) -> '\\'
+# \n (newline) -> '\n'
+# \t (tab character) -> '\t'
+#
+# there are three types of parameters:
+#
+# 1) simple strings:
+# <param name="parameter" value="string"/>
+#
+# 2) lists:
+# <param name="list">
+# <item value="item 1"/>
+# <item value="item 2"/>
+# </param>
+#
+# 3) mappings:
+# <param name="mapping">
+# <item name="item1" value="value 1"/>
+# <item name="item2" value="value 2"/>
+# </param>
+#
+# Next in the project comes the root group
+# each group can have:
+# - one config section (defined as above)
+# - some targets
+# - other groups nested in
+#
+# <group name="/" id="/">
+#
+# here the attribute 'name' is a printable name
+# and 'id' is a unique name to identify the group in the whole project
+# also 'id' is the path from the project root for the group
+# here's a nested group (nested groups appear before targets but after group config
+#
+# <group name="idl" id="/idl/">
+# <config>
+# <param name="installdirs">
+# <item name="idl" value="$(datadir)/idl/libgdl-1.0"/>
+# </param>
+# </config>
+# <target name="idl" type="data" id="idl:data">
+#
+# each target has a printable 'name' a unique 'id' (within the group) and a 'type'
+# conventionally id is name:type but that is subject to change
+# the targets also can have a config section
+#
+# <config>
+# <param name="installdir" value="idl"/>
+# </config>
+#
+# after the config section, comes the target sources and dependencies
+# the difference between a source and a dependency is that plain sources are
+# files available directly, and dependencies are files which are in turn
+# generated from other targets
+#
+# <source uri="/idl/GDL.idl"/>
+# <source uri="/idl/editor-buffer.idl"/>
+# <source uri="/idl/editor-gutter.idl"/>
+# <source uri="/idl/symbol-browser.idl"/>
+# </target>
+#
+# an example of a target with dependencies:
+#
+# <target name="libgdl-1.la" type="shared_lib" id="libgdl-1.la:shared_lib">
+# <source uri="/gdl/default-icon.c"/>
+# .
+# .
+# <source uri="/gdl/gdl-recent.c"/>
+# <dependency file="/gdl/libgdlmarshal.c" target="libgdlmarshal.c:rule"/>
+# .
+# .
+# <dependency file="/gdl/libgdltypebuiltins.c" target="libgdltypebuiltins.c:rule"/>
+# </target>
+#
+# the 'uri' attribute contains a path absolute to the project root for the file
+# the 'file' is the same as 'uri' but for dependencies, and 'target' is the target
+# 'id' as explained before (the targets belong to the same group)
+#
+#
+# Internal architecture:
+# ---------------------------------------------------------------------------------------
+#
+# All data is kept in anonymous hashes to emulate a C struct
+#
+# Meta structure definitions
+#
+# +--------------
+# | project : information about the project as a whole
+# +----------------
+# | prefix : string containing the root directory of the project
+# | root_group : root group hash (defined below)
+# | all_groups : flat hash containing all the groups for faster access
+# |
+#
+# +----------------
+# | group : group (or directory for this backend) information
+# +-----------------
+# | name : string, the name of the group
+# | makefile : makefile hash (see GBF/AmFiles.pm)
+# | targets : hash of target hashes (defined below)
+# | (target_name => target_hash)
+# | config : group_config hash for this directory
+# | groups : hash of subgroups hashes
+# | changed : the group_process procedure puts this flag to 1
+# |
+#
+# +----------------
+# | group_config : group configuration
+# +-----------------
+# | subdirs : SUBDIRS macro (order is important!!)
+# | includes (*) : INCLUDES macro
+# | ldadd (*) : LDADD macro
+# | compile (*) : COMPILE macro
+# | link (*) : LINK macro
+# | options (*) : AUTOMAKE_OPTIONS macro
+# | omit_deps (*) : OMIT_DEPENDENCIES macro
+# | built_sources : BUILT_SOURCES macro (expanded)
+# | extra_dist : EXTRA_DIST macro (expanded)
+# | cleanfiles (*) : CLEANFILES macro
+# | mostlyclean (*) : MOSTLYCLEANFILES macro
+# | maintainclean (*) : MAINTAINCLEANFILES macro
+# | distclean (*) : DISTCLEANFILES macro
+# | install_exec_local(*) : install-exec-local rule actions
+# | install_data_local(*) : install-data-local rule actions
+# | dist_hook (*) : dist-hook rule actions
+# | other_vars : hash of unused remaining Makefile.am macros after target
+# | extraction (macro_name => macro_contents)
+# | installdirs : hash containing user defined installation prefixes
+# | (prefix => installation_dir)
+# |
+#
+# +----------------
+# | target : target information
+# +-----------------
+# | id : id of the target (how it's identified in the group's hash
+# | name : string, name of the target
+# | type : type of the target
+# | sources : list of the source files which this target depends on
+# | dependencies : list of other targets this target depends on
+# | built_files : list of files presumably this target generates
+# | config : target_config hash (defined below)
+# |
+#
+# +----------------
+# | target_config : extra target configuration (these are all optional and
+# | dependent of the target type)
+# +-----------------
+# | ldflags (*) : for a primary target, target_LDFLAGS macro contents
+# | ldadd (*) : for a primary target, target_LDADD macro contents
+# | explicit_deps (*) : for a primary target, target_DEPENDENCIES macro contents
+# | actions (*) : rule actions if the target is derived from a makefile rule
+# | installdir : installation prefix (or noinst, check, EXTRA)
+# |
+#
+# (*) These fields are optional, they can be empty or undef
+#
+#
+# END DOCUMENTATION ------------------
+#
+##########################################################################################
+
+######################################################################
+#### 3. SOURCE EXTRACTION ##########################################
+######################################################################
+
+sub find_rules_for_target
+{
+ my $target = shift;
+ my $rules;
+ # FIXME: only takes first COLON or DCOLON
+ if (exists $target->{DCOLON})
+ {
+ $rules = $target->{DCOLON};
+ }
+ elsif (exists $target->{COLON})
+ {
+ $rules = $target->{COLON};
+ }
+ # If we only have one rule, make a 1-element array reference
+ if ((ref $rules) eq "Make::Rule")
+ {
+ my @rulearray = ($rules);
+ $rules = \ rulearray;
+ }
+ return $rules;
+}
+
+sub is_phony_target
+{
+ my ($target, $root) = @_;
+ return 0 unless exists $root->{Depend}{".PHONY"};
+
+ if (defined (my $rules = find_rules_for_target ($root->{Depend}{".PHONY"})))
+ {
+ foreach (@{$rules})
+ {
+ foreach (@{$_->depend})
+ {
+ return 1 if ($_ eq $target);
+ }
+ }
+ }
+ return 0;
+}
+
+sub find_source_suffixes
+{
+ my ($sr, $root) = @_;
+
+ if (defined (my $rules = find_rules_for_target ($root->{Depend}{$sr}) ))
+ {
+ my @suffixes;
+ foreach (@{$rules})
+ {
+ foreach (@{$_->depend})
+ {
+ my $sufdep = $_;
+ # if suffix dependency starts with a %, strip it
+ $sufdep =~ s/^\%//;
+
+ push @suffixes, $sufdep;
+ }
+ }
+ return @suffixes;
+ }
+ return undef;
+}
+
+sub find_sources_for_depname
+{
+ my ($depname, $path, $root) = @_;
+ my @sources = ();
+
+ # use suffix rules to find the sources
+ my @suffix;
+ my $depsuffix = $depname;
+ if ($depsuffix =~ /\.\w*$/)
+ {
+ $depsuffix =~ s/.*\./\./g;
+
+ # The dependency name has a suffix: check for %.blah rule
+ # Otherwise try a .blah rule
+ my $sr = '%' . $depsuffix;
+ if ($root->{Depend}{$sr})
+ {
+ @suffix = find_source_suffixes ($sr, $root);
+ }
+ elsif ($root->{Depend}{$depsuffix})
+ {
+ @suffix = find_source_suffixes ($depsuffix, $root);
+ }
+ }
+ else
+ {
+ # The dependency name has no suffix: check for % rule
+ if ($root->{Depend}{'%'})
+ {
+ @suffix = find_source_suffixes ('%', $root);
+ }
+ }
+
+ $depname =~ s/\.\w*$//;
+ foreach (@suffix)
+ {
+ my $srcname = $path . "/" . $depname . $_;
+
+ if ((-e $srcname) && (-T $srcname))
+ {
+ # add the source file to the list and finish
+ push @sources, $srcname;
+ last;
+ }
+ }
+
+ return (wantarray ? @sources : \ sources);
+}
+
+sub find_all_deps
+{
+ my ($target, $rules, $group, %loopdetect) = @_;
+ my %deps = ();
+ unless (defined %loopdetect) {%loopdetect = ();}
+
+ foreach (@{$rules})
+ {
+ foreach (@{$_->depend})
+ {
+ if (exists $loopdetect{$_})
+ {
+ &report_error (200, gettext("Loop detected in dependency graph"));
+ return undef;
+ }
+ $loopdetect{$_} = 1;
+ $deps{$_} = 1;
+
+ if (exists $group->{Depend}{$_})
+ {
+ # It's a target - find its deps and add them to the hash.
+ my $newtarget = $group->{Depend}{$_};
+ my $newrules = find_rules_for_target ($newtarget);
+
+ %deps = (%deps, find_all_deps ($newtarget, $newrules,
+ $group, %loopdetect));
+ }
+ }
+ }
+ return %deps;
+}
+
+# find_top_targets: find the top-level non-phony non-suffix targets to
+# build the project tree roots from
+
+sub find_top_targets
+{
+ my $root = shift;
+ my %targets = %{$root->{Depend}};
+ my %deps;
+
+ foreach (keys %targets)
+ {
+ # get rid of suffix and phony targets
+ if (($_ eq ".PHONY") || ($_ =~ /^[\.%]/))
+ {
+ delete $targets{$_};
+ next;
+ }
+
+ my $curr_targ = $root->{Depend}{$_};
+ %deps = find_all_deps ($curr_targ, find_rules_for_target($curr_targ),
+ $root);
+ delete @targets{keys %deps};
+ }
+
+ # Sort keys in same order as the Makefile targets
+ my @sorted = ();
+ foreach (@{$root->{Targets}})
+ {
+ if (exists $targets{$_})
+ {
+ push @sorted, $_;
+ }
+ }
+
+ return (%targets, @sorted);
+}
+
+sub create_group_from_depname
+{
+ my ($depname, $parent, $root) = @_;
+
+ my $path = $root->{Dir};
+ my $sources = find_sources_for_depname ($depname, $path, $root);
+
+ my $group = bless {Name => $depname,
+ Dir => $path,
+ Children => {},
+ Rules => {},
+ Sources => $sources,
+ Changed => 0,
+ Vars => {
+ Phony => is_phony_target ($depname, $root)
+ },
+ Id => "$parent->{Id}$depname/",
+ Parent => $parent,
+ Root => $root};
+
+ return $group;
+}
+
+sub create_group_from_target
+{
+ my ($target, $parent, $root) = @_;
+
+ my $group = create_group_from_depname ($target->Name, $parent, $root);
+ $group->{Rules} = find_rules_for_target ($target);
+ $group->{Target} = $target->Name;
+
+ # Add child groups
+
+ foreach (@{$group->{Rules}})
+ {
+ foreach (@{$_->depend})
+ {
+ if (exists $root->{Depend}{$_})
+ {
+ # we have a dependency which is itself a target
+ my $childtarget = $root->{Depend}{$_};
+ $group->{Children}{$_} = create_group_from_target ($childtarget,
+ $group, $root);
+ }
+ else
+ {
+ # we have a dependency built from suffix rules
+ $group->{Children}{$_} = create_group_from_depname($_,
+ $group, $root);
+ }
+ }
+ }
+
+ return $group;
+}
+
+sub build_makefile_tree
+{
+ my $root = shift;
+ my (%targets, @targ_sorted) = find_top_targets ($root);
+ my $group = bless {Name => "/",
+ Vars => $root->{Vars},
+ Children => {},
+ Changed => 0,
+ Dir => $root->{Dir},
+ Id => "/",
+ Root => $root};
+
+ foreach (keys %targets)
+ {
+ $group->{Children}{$_} = create_group_from_target($root->{Depend}{$_},
+ $root, $root);
+ }
+
+ return $group;
+}
+
+sub rec_print
+{
+ my ($root, $tabs) = @_;
+
+ print $tabs. "+" . $root->{Name} . "\n";
+ $tabs .= "\t";
+ if (exists ($root->{Sources}))
+ {
+ foreach (@{$root->{Sources}})
+ {
+ print "$tabs $_\n";
+ }
+ }
+ foreach (values %{$root->{Children}})
+ {
+ rec_print($_, $tabs);
+ }
+}
+
+######################################################################
+#### 4. GROUP AND PROJECT CONSTRUCTION #############################
+######################################################################
+
+sub process_project
+{
+ my $project_dir = $_[0];
+ my $mkfile_name = "Makefile";
+
+ &debug ("Using $project_dir as the project root");
+ if ( ! -f "$project_dir/Makefile") {
+ if ( ! -f "$project_dir/makefile") {
+ &report_error (1, gettext("Can't find $project_dir/Makefile or $project_dir/makefile"));
+ return undef;
+ }
+ $mkfile_name = "makefile";
+ };
+
+ my $project = { prefix => $project_dir,
+ mkfile => $mkfile_name,
+ root => {}};
+
+ # Recursively parse the Makefiles
+ $project->{root} = Make->new(Dir => $project_dir, Makefile => $mkfile_name);
+ $project->{root_group} = build_makefile_tree ($project->{root});
+
+ return $project;
+};
+
+
+######################################################################
+#### 5. PROJECT MODIFICATION #######################################
+######################################################################
+
+###
+# project_reset_changed (project)
+# reset groups' changed flag
+sub project_reset_changed
+{
+ my $project = shift;
+ foreach my $group (values %{$project->{all_groups}}) {
+ $group->{changed} = 0;
+ }
+}
+
+###
+# project_operate (project, operations)
+# perform indicated operations on project
+# returns: true if the project changed
+sub project_operate
+{
+ my ($project, $ops) = @_;
+ my $project_dirty = 0;
+
+ OP:
+ foreach my $op (@$ops) {
+ &debug ("($op->{op}, $op->{group_name}, ".
+ "$op->{target_name}, $op->{operand})");
+ my ($group, $target, $operand, $group_name, $result);
+
+ if ($op->{op} eq "add_group" || $op->{op} eq "remove_group") {
+ $operand = $op->{group_name};
+ $group_name = $operand;
+ $group_name =~ s/[^\/]+\/\z//;
+ &debug ("Adding a group $operand on group $group_name");
+ } else {
+ # Get the group from the project
+ $group_name = $op->{group_name};
+ }
+ $group = $project->{all_groups}{$group_name};
+
+ if (!$group) {
+ &report_error (303, gettext("The group $group_name doesn't exist"));
+ next OP;
+ }
+ &debug ("Using group $group->{name}");
+
+ # If the operation is on a group, do it now
+ ## if ($op->{op} =~ /group/) {
+ if ($op->{target_name} eq "") {
+ if (!defined($operand)) {
+ $operand = $op->{operand};
+ }
+ $result = &group_op_handler ($project, $group, $op->{op}, $operand);
+ $project_dirty = 1;
+ next OP;
+ }
+
+ # If not, continue with the parameters preparation
+ $operand = $op->{operand};
+ $target = $op->{target_name};
+ my $target_type = $operand;
+ if ($op->{op} ne "add_target" && $op->{op} !~ /group/) {
+ # Get the target hash from the group
+ $target = $group->{targets}{$target};
+ if (! defined ($target)) {
+ &report_error (304, gettext("The target $op->{target_name} doesn't exist"));
+ next OP;
+ }
+ $target_type = $target->{type};
+ }
+
+ # Get the target writer
+# my $writer = $target_writers{$target_type};
+# $result = &$writer ($project, $group, $op->{op}, $target, $operand);
+# if ($result == 0) {
+# $project_dirty = 1;
+# }
+ }
+
+ if ($project_dirty) {
+ # update flat hash first
+ &project_update_all_groups ($project);
+
+ # write configure.in
+ if ($project->{configure_in}{dirty}) {
+ output_configure_in $project->{configure_in};
+ # FIXME: possibly reprocess configure.in information
+ };
+
+ foreach my $group (values %{$project->{all_groups}}) {
+ if ($group->{makefile}{dirty}) {
+ my $makefile = $group->{makefile};
+ &debug ("$makefile->{filename} is dirty");
+
+ # output modified makefile.am if not running in test mode
+ &output_am_file ($makefile, $makefile->{filename}) unless ($dry_run);
+
+ # reprocess group
+ &group_process ($group);
+
+ $makefile->{dirty} = 0;
+ };
+ };
+ };
+
+ return $project_dirty;
+}
+
+
+######################################################################
+#### 6. TARGET WRITER FUNCTIONS ####################################
+######################################################################
+
+sub group_op_handler
+{
+ my ($project, $group, $op, $operand) = @_;
+ my $makefile = $group->{makefile};
+
+ # group is the group hash in which to do the operation
+ # op is: "add_group" or "remove_group"
+ # operand is full group name for add_group, and ignored for remove_group
+
+ if ($op eq "add_group") {
+ my @components = split '/', $operand;
+ my $group_name = "";
+ $group_name = pop @components while ($group_name eq "");
+
+ # check that the group doesn't exist already
+ if (defined $group->{groups}{$group_name}) {
+ return &report_error (305, gettext("Group $operand already exists"));
+ };
+
+ # create the directory and an empty Makefile.am
+ my $local_new_dir = $group->{prefix} . $group_name;
+ my $new_dir = $project->{prefix} . $local_new_dir;
+
+ unless (-d $new_dir) {
+ mkdir $new_dir || return &report_error (304, gettext("Can't mkdir $new_dir"));
+ };
+ unless (-f "$new_dir/Makefile.am") {
+ if (open (NEWFILE, ">$new_dir/Makefile.am")) {
+ print NEWFILE "## File created by the gnome-build tools\n\n\n";
+ close NEWFILE;
+ } else {
+ return &report_error (304, gettext("Can't write $new_dir/Makefile.am"));
+ };
+ };
+
+ # add new directory to the SUBDIRS macro in the parent group
+ ¯o_append_text ($makefile, "SUBDIRS", $group_name);
+
+ # create the internal representation of the group
+ $group->{groups}{$group_name} = &create_group ($operand, "$group_name", $project);
+
+ # add Makefile to list of configure generated files
+ $local_new_dir =~ s/^\///;
+ edit_config_files $project->{configure_in}, "add", "$local_new_dir/Makefile";
+ }
+ elsif ($op eq "remove_group") {
+ my @components = split '/', $operand;
+ my $group_name = "";
+
+ $group_name = pop @components while ($group_name eq "");
+
+ # check that the group doesn't exist already
+ if (!defined $group->{groups}{$group_name}) {
+ return &report_error (305, gettext("Group $operand does not exists"));
+ };
+
+ # remove directory from the SUBDIRS macro in the parent group
+ ¯o_remove_text ($makefile, "SUBDIRS", $group_name, 1);
+
+ # add Makefile to list of configure generated files
+ my $local_new_dir = $group->{prefix} . $group_name;
+ $local_new_dir =~ s/^\///;
+ edit_config_files $project->{configure_in}, "remove", "$local_new_dir/Makefile";
+
+ ## Remove group from internal hashes
+ delete $project->{all_groups}->{$operand};
+ delete $group->{groups}->{$group_name};
+ }
+ elsif ($op eq "set_config") {
+ KEY:
+ foreach my $key (keys %$operand) {
+ my $value = $operand->{$key};
+
+ if ($key eq "includes") {
+ ¯o_rewrite ($makefile, "INCLUDES", $value);
+ } elsif ($key eq "installdirs") {
+ foreach my $item (keys %$value) {
+ ¯o_rewrite ($makefile, $item."dir", $value->{$item});
+ }
+ }
+ }
+ }
+ else {
+
+ return &report_error (300, gettext("Invalid operation '$op' to group_op_handler"));
+ }
+ # success!
+ return 0;
+}
+
+sub unimplemented_writer
+{
+ my ($project, $group, $op, $target, $operand) = @_;
+
+ # group is the group hash in which to do the operation
+ # op is: "add_target", "remove_target", "set_config", "add_source", "remove_source"
+ # target is:
+ # - the new target name when add_target
+ # - a ref to the target hash to operate on otherwise
+ # operand is:
+ # - ignored for remove_target
+ # - target type for add_target
+ # - the source uri to add/remove on {add,remove}_source
+ # - a config hash on set_config containing any/all fields in group config
+ # FIXME: handle dependencies
+
+ return &report_error (302, gettext("Unimplemented target type writer"));
+}
+
+sub compiled_primary_target_writer
+{
+ my ($project, $group, $op, $target, $operand) = @_;
+ my $makefile = $group->{makefile};
+
+ my %primaries = ( program => "PROGRAMS",
+ static_lib => "LIBRARIES",
+ shared_lib => "LTLIBRARIES" );
+
+ &debug ("$op on group $group with $target($operand)");
+
+ # FIXME: Modify the $group variable as well as the makefile (worth it?...
+ # only if we have to do a sequence of operations on the same target)
+
+ if ($op eq "add_target") {
+ # $target contains the target name, which in this case is the name of
+ # the compiled objects (program or library)
+ # $operand contains the target type
+ my $canonical = &canonicalize_name ($target);
+ my ($primary, $prefix);
+
+ my %default_prefixes = ( program => "noinst",
+ static_lib => "noinst",
+ shared_lib => "lib" );
+
+ $primary = $primaries{$operand};
+ $prefix = $default_prefixes{$operand};
+
+ if ($target eq "") {
+ return &report_error (301, gettext("Invalid empty target name"));
+ }
+
+ if (!defined $primary) {
+ return &report_error (301, gettext("Invalid target type '$operand' to compiled_primary_target_writer"));
+ }
+
+ # FIXME: verify that the target doesn't yet exist
+ ¯o_append_text ($makefile, "${prefix}_${primary}", $target);
+ ¯o_create ($makefile, "${canonical}_SOURCES", "", "${prefix}_${primary}");
+
+ }
+ elsif ($op eq "remove_target") {
+ my $canonical = &canonicalize_name ($target->{name});
+ my $prefix = $target->{config}{installdir};
+ my $primary = $primaries{$target->{type}};
+
+ # FIXME: what if the target is in a macro which is used in prefix_PRIMARY
+ ¯o_remove_text ($makefile, "${prefix}_${primary}", $target->{name}, 1);
+ ¯o_remove_text ($makefile, "EXTRA_${primary}", $target->{name}, 1);
+ ¯o_remove ($makefile,
+ "${canonical}_SOURCES",
+ "EXTRA_${canonical}_SOURCES",
+ "${canonical}_LDADD",
+ "${canonical}_LIBADD",
+ "${canonical}_LDFLAGS",
+ "${canonical}_DEPENDENCIES");
+ ## FIXME: The line below reports error.
+ ## &rule_remove ($makefile, "\$(${canonical}_OBJECTS)");
+
+ ## Remove the target from internal hash
+ my $target_id = $target->{id};
+ delete $group->{targets}->{$target_id};
+ }
+ elsif ($op eq "set_config") {
+ my $canonical = &canonicalize_name ($target->{name});
+ my $primary = $primaries{$target->{type}};
+
+ KEY:
+ foreach my $key (keys %$operand) {
+ my $value = $operand->{$key};
+
+ if ($key eq "installdir") {
+ my $old_prefix = $target->{config}{installdir};
+ my $new_prefix = &check_primary_prefix ($value, $primary, $group);
+ if (!$new_prefix) {
+ next KEY;
+ }
+
+ # FIXME: handle the EXTRA prefix
+ &debug ("Removing $target->{name} from ${old_prefix}_${primary}");
+ ¯o_remove_text ($makefile, "${old_prefix}_${primary}",
+ $target->{name}, 1);
+ ¯o_append_text ($makefile, "${new_prefix}_${primary}",
+ $target->{name});
+ } elsif ($key eq "ldflags") {
+ if ($value ne "") {
+ ¯o_rewrite ($makefile, "${canonical}_LDFLAGS",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_LDFLAGS");
+ };
+ } elsif ($key eq "ldadd") {
+ # Get rid of deprecated variable
+ ¯o_remove ($makefile, "${canonical}_LIBADD");
+ if ($value ne "") {
+ ¯o_rewrite ($makefile, "${canonical}_LDADD",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_LDADD");
+ };
+ } elsif ($key eq "explicit_deps") {
+ # FIXME: this should perhaps be handled via sources add/remove
+ if ($value ne "") {
+ ¯o_rewrite ($makefile, "${canonical}_DEPENDENCIES",
+ $value, "${canonical}_SOURCES");
+ } else {
+ ¯o_remove ($makefile, "${canonical}_DEPENDENCIES");
+ };
+ }
+ }
+ }
+ elsif ($op eq "add_source") {
+ my $canonical = &canonicalize_name ($target->{name});
+ my $var = "${canonical}_SOURCES";
+ my $rel_source;
+
+ # Note: we expect the operand to be the absolute to the root of the project
+ # i.e. for group /src/, the operand will be /src/file.c
+ $rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+ &debug ("Will modify $var using source $rel_source");
+ my %macros = %{$makefile->{macros}};
+ if (!exists ($macros{$var})) {
+ # Need to add the default source the the new var
+ $operand = "$target->{name}.c " . $rel_source;
+ }
+ ¯o_append_text ($makefile, $var, $rel_source);
+
+ }
+ elsif ($op eq "remove_source") {
+ my $canonical = &canonicalize_name ($target->{name});
+ my $var = "${canonical}_SOURCES";
+ my $rel_source;
+
+ # Note: we expect the operand to be the absolute to the root of the project
+ # i.e. for group /src/, the operand will be /src/file.c
+
+ # FIXME: this needs lot of work, since only works for literal filnanmes
+ # we need to identify where is this filename in expanded from a macro or
+ # if it has some variable like $(srcdir)
+ $rel_source = path_absolute_to_relative ($group->{prefix}, $operand);
+
+ # FIXME: check for duplicate sources
+
+ &debug ("Will modify $var using source $rel_source");
+ if (!exists ($makefile->{macros}{$var}) &&
+ $rel_source eq "${$target->{name}}.c") {
+ # Create the empty macro if the removed source is the default one
+ ¯o_create ($makefile, $var, "");
+ } else {
+ ¯o_remove_text ($makefile, $var, $rel_source);
+ }
+
+ }
+ else {
+ return &report_error (300, gettext("Invalid operation '$op' to program_target_writer"));
+ };
+
+ # Success!
+ return 0;
+}
+
+
+sub simple_primary_target_writer
+{
+ my ($project, $group, $op, $target, $operand) = @_;
+ my $makefile = $group->{makefile};
+
+ my %primaries = ( data => "DATA",
+ scripts => "SCRIPTS",
+ headers => "HEADERS" );
+
+ &debug ("$op on group $group with $target($operand)");
+
+ # FIXME: Modify the $group variable as well as the makefile (worth it?...
+ # only if we have to do a sequence of operations on the same target)
+
+ if ($op eq "add_target") {
+ # WARNING, Convention: $target for simple primaries is actually $prefix:$type
+ # $operand also contains the target type
+ my ($primary, $prefix, $check_primary);
+
+ ($prefix, $check_primary) = split /:/, $target;
+
+ $primary = $primaries{$operand};
+ if (!defined $primary) {
+ return &report_error (301, gettext("Invalid target type '$operand' to simple_primary_target_writer"));
+ }
+
+ if ($check_primary ne $operand) {
+ &report_warning (302, gettext("The target type supplied in the target name $target and the given target type '$operand' don't match. Will use the one provided in the name"));
+ }
+
+ if (!&check_primary_prefix ($prefix, $primary, $group)) {
+ return;
+ }
+
+ ¯o_create ($makefile, "${prefix}_${primary}", "");
+
+ }
+ elsif ($op eq "remove_target") {
+ my $prefix = $target->{config}{installdir};
+ my $primary = $primaries{$target->{type}};
+
+ ¯o_remove ($makefile, "${prefix}_${primary}");
+ }
+ elsif ($op eq "set_config") {
+ # Nothing to do here, since these kind of targets are not relocatable
+
+ }
+ elsif ($op eq "add_source") {
+ my $prefix = $target->{config}{installdir};
+ my $primary = $primaries{$target->{type}};
+ my $var = "${prefix}_${primary}";
+
+ # FIXME: add file to EXTRA_DIST if it's a DATA primary and the file is not
+ # a built file
+ # Note: we expect the uri to be relative to the group
+ ¯o_append_text ($makefile, $var, $operand);
+
+ }
+ elsif ($op eq "remove_source") {
+ my $prefix = $target->{config}{installdir};
+ my $primary = $primaries{$target->{type}};
+ my $var = "${prefix}_${primary}";
+
+ # Note: we expect the uri to be relative to the group
+ ¯o_remove_text ($makefile, $var, $operand);
+
+ }
+ else {
+ return &report_error (300, gettext("Invalid operation '$op' to program_target_writer"));
+ };
+
+ # Success!
+ return 0;
+}
+
+
+######################################################################
+#### 7. XML PRINTING FUNCTIONS #####################################
+######################################################################
+
+my ($indent_level, $have_vspace);
+
+$indent_level = 0;
+$have_vspace = 0;
+
+sub xml_enter { $indent_level += 2; }
+sub xml_leave { $indent_level -= 2; }
+sub xml_indent { print " " x $indent_level; $have_vspace = 0; }
+sub xml_vspace { if (not $have_vspace) { print "\n"; $have_vspace = 1; } }
+sub xml_print { &xml_indent; print @_; }
+
+sub xml_escape
+{
+ $_ = $_[0];
+ s/\&/\&/g;
+ s/\</\</g;
+ s/\>/\>/g;
+ s/\"/\"/g;
+ ## s/\\/\\\\/g;
+ ## s/\n/\\n/g;
+ ## s/\t/\\t/g;
+ return $_;
+}
+
+sub xml_unescape
+{
+ $_ = $_[0];
+ s/\&/\&/g;
+ s/\</\</g;
+ s/\>/\>/g;
+ s/\"/\"/g;
+ ## s/\\\\/\\/g;
+ ## s/\\n/\n/g;
+ ## s/\\t/\t/g;
+ return $_;
+}
+
+sub xml_print_source
+{
+ my $source = shift;
+
+ &xml_enter ();
+ &xml_print ("<source uri=\"" . reduce_path ($source) . "\"/>\n");
+ &xml_leave ();
+}
+
+sub xml_print_dep
+{
+ my ($dep, $prefix) = @_;
+
+ my ($target, $file) = split ';', $dep;
+ &xml_enter ();
+ # FIXME: uris should be absolute to the project root (i.e. ../ must be reduced)
+ &xml_print ("<dependency file=\"" .
+ reduce_path ($prefix . "/" . $target) . "\"/>\n");
+ &xml_leave ();
+}
+
+sub xml_print_target
+{
+ my $group = shift;
+
+ &xml_enter ();
+
+ my $type = "program";
+ &xml_print ("<target name=\"$group->{Target}\" ".
+ "id=\"$group->{Target}\" type=\"$type\">\n");
+
+ if (exists $group->{Sources})
+ {
+ foreach (@{$group->{Sources}}) {
+ &xml_print_source ($_);
+ }
+ }
+
+ &xml_print ("</target>\n");
+
+ &xml_leave ();
+}
+
+###
+# group_print_xml (group [, which])
+# recursively prints the xml representation of the group
+# if which is "changed" only prints the group if it's flaged as changed
+sub group_print_xml
+{
+ my $group = shift;
+ my $which = shift || "all";
+
+ if ($which ne "changed" || $group->{Changed})
+ {
+ &xml_enter ();
+ &xml_print ("<group name=\"$group->{Name}\" id=\"$group->{Id}\" " .
+ "source=\"$group->{Dir}/Makefile\">\n");
+
+ # Print the group config
+ &xml_print_config ($group->{Vars}, ());
+
+ # Print child groups
+ foreach (values %{$group->{Children}}) {
+ &group_print_xml ($_, $which);
+ }
+
+ # Print the target
+ if (exists $group->{Target})
+ {
+ &xml_print_target ($group);
+ }
+
+ &xml_print ("</group>\n");
+
+ &xml_leave ();
+ }
+}
+
+sub xml_print_config
+{
+ my ($config, @except) = @_;
+ my $value;
+
+ &xml_enter ();
+ &xml_print ("<config>\n");
+ foreach my $key (keys %{$config}) {
+ if (grep $_ eq $key, @except) {
+ next;
+ };
+
+ &xml_enter ();
+ $value = &empty_if_undef ($config->{$key});
+ if (ref ($value) eq "ARRAY" && @$value) {
+ # It's a list
+ &xml_print ("<param name=\"$key\">\n");
+ &xml_enter ();
+ foreach (@$value) {
+ ## Remove white spaces from config parameters.
+ $_ =~ s/\s/ /gs;
+ $_ =~ s/\s+/ /gs;
+ $_ = &xml_escape ($_);
+ &xml_print ("<item value=\"$_\"/>\n");
+ };
+ &xml_leave ();
+ &xml_print ("</param>\n");
+ } elsif (ref ($value) eq "HASH" && %$value) {
+ # It's a hash
+ &xml_print ("<param name=\"$key\">\n");
+ &xml_enter ();
+ foreach my $item (keys %$value) {
+ ## Remove white spaces from config parameters.
+ my $val = $value->{$item};
+ $val =~ s/\s/ /gs;
+ $val =~ s/\s+/ /gs;
+ $_ = &xml_escape ($val);
+ &xml_print ("<item name=\"$item\" value=\"$_\"/>\n");
+ };
+ &xml_leave ();
+ &xml_print ("</param>\n");
+ } elsif (! ref ($value) && $value) {
+ ## Remove white spaces from config parameters.
+ $value =~ s/\s/ /gs;
+ $value =~ s/\s+/ /gs;
+ $value = &xml_escape ($value);
+ &xml_print ("<param name=\"$key\" value=\"$value\"/>\n");
+ };
+ &xml_leave ();
+ };
+ &xml_print ("</config>\n");
+ &xml_leave ();
+}
+
+###
+# project_print_xml (project [, which])
+# print xml representation of the project
+# if which is "changed" only prints changed groups in last modification
+sub project_print_xml
+{
+ my $project = shift;
+ my $which = shift || "all";
+
+ print "<?xml version='1.0' encoding='ISO-8859-1' standalone='yes'?>\n";
+ print "<!DOCTYPE project []>\n\n";
+ print "<project root=\"$project->{prefix}\" report=\"" .
+ ($which eq "changed" ? "partial" : "full") .
+ "\" source=\"$project->{prefix}/$project->{mkfile}\">\n";
+
+ # Print the groups
+ &group_print_xml ($project->{root_group}, $which);
+
+ print "</project>\n";
+}
+
+sub recursive_print
+{
+ my ($level, $el) = @_;
+ my $pad = " " x (2 * $level);
+ if (!ref ($el)) {
+ print $pad, $el, "\n";
+ } elsif (ref ($el) eq "ARRAY") {
+ foreach my $sel (@$el) {
+ &recursive_print ($level + 1, $sel);
+ }
+ } elsif (ref ($el) eq "HASH") {
+ foreach my $sel (keys %$el) {
+ print $pad, $sel, " => ", $el->{$sel}, "\n";
+ }
+ }
+}
+
+
+######################################################################
+#### 8. XML SCANNING ###############################################
+######################################################################
+
+sub xml_scan_make_kid_array
+{
+ my %hash = ();
+ my (@sublist, @attr);
+
+ @attr = $_[0] =~ /[^\s]+\s*([a-zA-Z_-]+)\s*\=\s*\"([^\"]*)/g;
+ %hash = @attr;
+
+ push @sublist, \%hash;
+ return \ sublist;
+}
+
+sub xml_scan_recurse
+{
+ my ($tree, $scan_ref) = @_;
+
+ my @list = @$tree;
+ my ($el, $sublist);
+
+ while (@$scan_ref) {
+ $el = shift @$scan_ref;
+
+ # Empty strings, PI and DTD must go.
+ if (($el eq "") || $el =~ /^\<[!?].*\>$/s) { next; }
+
+ if ($el =~ /^\<.*\/\>$/s) {
+ # Empty.
+ $el =~ /^\<([a-zA-Z_-]+).*\/\>$/s;
+ push @list, $1;
+ push @list, &xml_scan_make_kid_array ($el);
+
+ } elsif ($el =~ /^\<\/.*\>$/s) {
+ # End.
+ last;
+
+ } elsif ($el =~ /^\<.*\>$/s) {
+ # Start.
+ $el =~ /^\<([a-zA-Z_-]+).*\>$/s;
+ push @list, $1;
+ $sublist = &xml_scan_make_kid_array ($el);
+ push @list, &xml_scan_recurse ($sublist, $scan_ref);
+ next;
+
+ } elsif ($el ne "") {
+ # PCDATA.
+ push @list, 0;
+ push @list, "$el";
+ }
+ }
+
+ return \ list;
+}
+
+sub xml_scan
+{
+ my $input_file = $_[0];
+ my ($doc, $tree, $i);
+
+ if (!$input_file || $input_file eq "-") {
+ $doc .= $i while ($i = <STDIN>);
+
+ } else {
+ if (open INPUT_FILE, $input_file) {
+ $doc .= $i while ($i = <INPUT_FILE>);
+ close INPUT_FILE;
+ }
+ else {
+ &report_error (4, gettext("Can't open input file '$input_file'"));
+ return [];
+ }
+ }
+
+ &debug ("Got input: $doc");
+
+ my @xml_scan_list = ($doc =~ /([^\<]*)(\<[^\>]*\>)[ \t\n\r]*/mg); # pcdata, tag, pcdata, tag, ...
+
+ $tree = &xml_scan_recurse ([], \ xml_scan_list);
+
+ return $tree;
+}
+
+
+######################################################################
+#### 9. XML PROCESSING #############################################
+######################################################################
+
+sub xml_parse_params
+{
+ my $tree = $_[0];
+ my %params;
+
+ shift @$tree;
+
+ while ($$tree[0] eq "param") {
+ shift @$tree;
+ my $child = $$tree[0];
+ if (defined($$child[0]->{value})) {
+ my $str = $$child[0]->{value};
+ $params{$$child[0]->{name}} = xml_unescape ($str);
+ } else {
+ my %items;
+ $params{$$child[0]->{name}} = \%items;
+ shift @$child;
+ while ($$child[0] eq "item") {
+ shift @$child;
+ my $str = $$child[0][0]->{value};
+ $items{$$child[0][0]->{name}} = xml_unescape ($str);
+ shift @$child;
+ }
+ }
+ shift @$tree;
+ }
+ return \%params;
+}
+
+sub xml_parse_add
+{
+ my $tree = $_[0];
+ my $type;
+ my ($parent, $curr);
+
+ my %new_op = ( op => "",
+ group_name => "",
+ target_name => "",
+ operand => "" );
+
+ $type = $$tree[0]->{type};
+ $new_op{op} = "add_$type";
+
+ shift @$tree;
+
+ $parent = $$tree[0];
+ $curr = $$tree[1];
+
+ while (1) {
+ if ($parent eq "group") {
+ # The group id is unique
+ $new_op{group_name} = $$curr[0]->{id};
+ } elsif ($parent eq "target") {
+ $new_op{target_name} = $$curr[0]->{id};
+ if ($type eq "target") {
+ $new_op{operand} = $$curr[0]->{type};
+ }
+ } elsif ($parent eq "source") {
+ $new_op{operand} = $$curr[0]->{uri};
+ }
+
+ if ($parent eq $type) {
+ last;
+ }
+
+ shift @$curr;
+ $parent = $$curr[0];
+ $curr = $$curr[1];
+ }
+
+ return \%new_op;
+}
+
+sub xml_parse_change
+{
+}
+
+sub xml_parse_set
+{
+ my $tree = $_[0];
+ my $type;
+ my ($parent, $curr);
+
+ my %new_op = ( op => "",
+ group_name => "",
+ target_name => "",
+ operand => "" );
+ $new_op{operand} = {};
+
+ $type = $$tree[0]->{type};
+ $new_op{op} = "set_$type";
+
+ shift @$tree;
+
+ $parent = $$tree[0];
+ $curr = $$tree[1];
+
+ while (1) {
+ if ($parent eq "group") {
+ $new_op{group_name} = $$curr[0]->{id};
+ } elsif ($parent eq "target") {
+ $new_op{target_name} = $$curr[0]->{id};
+ } elsif ($parent eq "config") {
+ $new_op{operand} = xml_parse_params($curr);
+ }
+
+ if ($parent eq $type) {
+ last;
+ }
+
+ shift @$curr;
+ $parent = $$curr[0];
+ $curr = $$curr[1];
+ }
+
+ return \%new_op;
+}
+
+sub xml_parse_remove
+{
+ my $tree = $_[0];
+ my $type;
+ my ($parent, $curr);
+
+ my %new_op = ( op => "",
+ group_name => "",
+ target_name => "",
+ operand => "" );
+
+ $type = $$tree[0]->{type};
+ $new_op{op} = "remove_$type";
+
+ shift @$tree;
+
+ $parent = $$tree[0];
+ $curr = $$tree[1];
+
+ while (1) {
+ if ($parent eq "group") {
+ # The group name is unique
+ $new_op{group_name} = $$curr[0]->{id};
+ } elsif ($parent eq "target") {
+ $new_op{target_name} = $$curr[0]->{id};
+ } elsif ($parent eq "source") {
+ $new_op{operand} = $$curr[0]->{uri};
+ }
+
+ if ($parent eq $type) {
+ last;
+ }
+
+ shift @$curr;
+ $parent = $$curr[0];
+ $curr = $$curr[1];
+ }
+ return \%new_op;
+}
+
+sub xml_parse_ops
+{
+ my $tree = $_[0];
+ my @ops;
+
+ shift @$tree; # Skip attributes.
+
+ while (@$tree) {
+ if ($$tree[0] eq "add") {
+ push @ops, &xml_parse_add ($$tree[1]);
+ }
+ elsif ($$tree[0] eq "change") {
+ push @ops, &xml_parse_change ($$tree[1]);
+ }
+ elsif ($$tree[0] eq "set") {
+ push @ops, &xml_parse_set ($$tree[1]);
+ }
+ elsif ($$tree[0] eq "remove") {
+ push @ops, &xml_parse_remove ($$tree[1]);
+ }
+
+ shift @$tree;
+ shift @$tree;
+ }
+
+ return \ ops;
+}
+
+
+######################################################################
+#### 10. HELPER FUNCTIONS ##########################################
+######################################################################
+
+sub path_relative_to_absolute
+{
+ my ($prefix, $rel) = @_;
+
+ if (substr ($prefix, -1) ne "/") {
+ $prefix .= "/";
+ };
+
+ return reduce_path ($prefix . $rel);
+}
+
+sub path_absolute_to_relative
+{
+ my ($prefix, $absolute) = @_;
+
+ my @prefix_parts = split '/', $prefix;
+ my @absolute_parts = split '/', $absolute;
+ my @result = ();
+ my $append_parent = 0;
+
+ foreach my $part (@prefix_parts) {
+ my $abs_part = shift @absolute_parts;
+
+ if ($part eq $abs_part && !$append_parent) {
+ next;
+ } else {
+ $append_parent = 1;
+ unshift @result, "..";
+ push @result, $abs_part;
+ };
+
+ };
+ push @result, @absolute_parts;
+
+ return join ('/', @result);
+}
+
+sub reduce_path
+{
+ my ($uri) = @_;
+
+ my @result = ();
+
+ foreach my $part (split '/', $uri) {
+ if ($part eq "..") {
+ pop @result;
+ } elsif ($part eq ".") {
+ next;
+ } else {
+ push @result, $part;
+ };
+ };
+
+ return (join '/', @result);
+}
+
+sub remove_files_from_sources
+{
+ my ($var, $group) = @_;
+
+ my %targets = %{$group->{targets}};
+
+ # Strips from $var all those files already present in some target's sources
+ $var = " $var ";
+ foreach my $target (values %targets) {
+ foreach my $source (@{$target->{sources}}) {
+ $var =~ s/\s\Q$source\s/ /g;
+ };
+ };
+ return &trim ($var);
+}
+
+sub remove_files_from_built_files
+{
+ my ($var, $group) = @_;
+
+ my %targets = %{$group->{targets}};
+
+ # Strips from $var all those files already present in some target's built_file,
+ # returning the target name
+ my @targets_found;
+ $var = " $var ";
+ foreach my $target (keys %targets) {
+ foreach my $built_file (@{$targets{$target}{built_files}}) {
+ if ($var =~ /\s\Q$built_file\E\s/) {
+ $var =~ s/\s\Q$built_file\E\s/ /g;
+ push @targets_found, "$target;$built_file";
+ };
+ };
+ };
+ # Now remove those files present in BUILT_SOURCES
+ foreach my $built_file (split /\s+/, $group->{config}{built_sources}) {
+ $var =~ s/\s$built_file\s/ /g;
+ }
+
+ return (&trim ($var), @targets_found);
+}
+
+sub get_sources_by_extension
+{
+ my ($group, $ext) = @_;
+
+ my @result;
+
+ foreach my $target (values %{$group->{targets}}) {
+ my @sources = @{$target->{sources}};
+ push @result, grep /$ext\z/, @sources;
+ };
+ return @result;
+}
+
+sub make_absolute_path
+{
+ my $path = $_[0];
+
+ if (substr ($path, 0, 1) ne "/") {
+ # the path is not absolute
+ my $cwd = `pwd`;
+ chomp $cwd;
+ $path = "$cwd/$path";
+ };
+
+ return reduce_path ($path);
+}
+
+######################################################################
+#### 11. MAIN PROGRAM ##############################################
+######################################################################
+
+my ($op, $arg, $newop);
+
+$op = "";
+while (@ARGV) {
+ $_ = shift @ARGV;
+ if ($_ eq "--get" || $_ eq "-g") { $newop = "get"; }
+ elsif ($_ eq "--set" || $_ eq "-s") { $newop = "set"; }
+ elsif ($_ eq "--verbose" || $_ eq "-v") { $verbose = 1; }
+ elsif ($_ eq "--dry-run" || $_ eq "-n") { $dry_run = 1; }
+ elsif ($_ eq "--debug" || $_ eq "-d") { $debug = 1; }
+
+ elsif ($_ eq "--test-scan") { $newop = "test-scan"; }
+ else {
+ if ($arg) {
+ &report_error (5, gettext("You can't specify more than one project dir/file"));
+ exit 5;
+ }
+ $arg = $_;
+ }
+ if ($newop) {
+ if ($op) {
+ &report_error (5, gettext("You can't specify more than one operation"));
+ exit 5;
+ }
+ $op = $newop;
+ $newop = "";
+ }
+}
+
+if ($op eq 'get') {
+ #########################################
+ # Get the project XML view
+ #########################################
+
+ my $project_dir = make_absolute_path ($arg);
+
+ if (!$project_dir || ! -d $project_dir || ! -e $project_dir) {
+ &report_error (2, gettext("Project root directory doesn't exist"));
+ exit 2;
+ };
+
+ my $project = &process_project ($project_dir);
+
+ if ($project)
+ {
+ &project_print_xml ($project);
+
+# rec_print($project->{root_group});
+ };
+}
+
+elsif ($op eq "test-scan") {
+ #########################################
+ # Test XML scanning
+ #########################################
+
+ my $tree = &xml_scan ($arg);
+ &recursive_print (-1, $tree);
+
+}
+elsif ($op eq "set") {
+ ########################################
+ # Project modification
+ ########################################
+
+ my $tree = &xml_scan ($arg);
+
+ # Walk the tree recursively and extract configuration parameters.
+
+ while (@$tree) {
+ if ($$tree[0] eq "project") {
+ # Get the project and execute
+ my $project_dir = make_absolute_path ($$tree [1][0]->{root});
+ my $project = &process_project ($project_dir);
+ my $ops = &xml_parse_ops ($$tree[1]);
+ if ($project && $ops) {
+ # reset groups changed flag so we later know what
+ # groups changed and need to be fed back to the user
+ &project_reset_changed ($project);
+
+ # perform the operations
+ if (&project_operate ($project, $ops)) {
+ # print changed groups
+ &project_print_xml ($project, "changed");
+ }
+ };
+ }
+ shift @$tree;
+ shift @$tree;
+ }
+}
+else {
+ &report_warning (0, gettext("Nothing to do"));
+}
Added: trunk/plugins/gbf-mkfile/gbf-mkfile-plugin-48.png
==============================================================================
Binary file. No diff available.
Added: trunk/plugins/gbf-mkfile/gbf-mkfile-project.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-project.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,3510 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-mkfile-project.c
+ *
+ * Copyright (C) 2005 Eric Greveson
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ * JP Rosevear
+ * Dave Camp
+ * Naba Kumar
+ * Gustavo GirÃldez
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <glib/gi18n.h>
+#include <libgnome/gnome-macros.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libgnomevfs/gnome-vfs-monitor.h>
+#include <libgnomevfs/gnome-vfs-ops.h>
+#include <libgnomevfs/gnome-vfs-utils.h>
+#include <libanjuta/gbf-project.h>
+#include "gbf-mkfile-project.h"
+#include "gbf-mkfile-config.h"
+#include "gbf-mkfile-properties.h"
+
+#define ENABLE_DEBUG
+
+#ifdef ENABLE_DEBUG
+#define DEBUG(x) x
+#else
+#define DEBUG(x)
+#endif
+
+
+#define UNIMPLEMENTED G_STMT_START { g_warning (G_STRLOC": unimplemented"); } G_STMT_END
+
+/* Constant strings for parsing perl script error output */
+#define ERROR_PREFIX "ERROR("
+#define WARNING_PREFIX "WARNING("
+#define MESSAGE_DELIMITER ": "
+
+
+/* ----- Queue data types ----- */
+
+/* FIXME: extend and make most operations asynchronous */
+typedef enum {
+ BUILD
+} GbfMkfileProjectOpType;
+
+typedef struct {
+ GbfMkfileProject *project;
+ GbfMkfileProjectOpType type;
+ gchar *build_id;
+} GbfMkfileProjectOp;
+
+
+/* ----- Change sets data types ----- */
+
+typedef struct _GbfMkfileChange GbfMkfileChange;
+
+typedef enum {
+ GBF_MKFILE_CHANGE_ADDED,
+ GBF_MKFILE_CHANGE_REMOVED
+} GbfMkfileChangeType;
+
+struct _GbfMkfileChange {
+ GbfMkfileChangeType change;
+ GbfMkfileNodeType type;
+ gchar *id;
+};
+
+
+/* ----- XML output parser data types ----- */
+
+typedef struct _GbfMkfileProjectParseData GbfMkfileProjectParseData;
+
+struct _GbfMkfileProjectParseData {
+ GbfMkfileProject *project;
+
+ /* For tracking state */
+ GNode *current_node;
+ gint depth; /* group depth only */
+ GbfMkfileConfigMapping *config;
+ gchar *param_key;
+ gboolean full_report;
+
+ /* parser state */
+ enum {
+ PARSE_INITIAL,
+ PARSE_DONE,
+
+ PARSE_PROJECT,
+ PARSE_GROUP,
+ PARSE_TARGET,
+ PARSE_SOURCE,
+ PARSE_DEPENDENCY,
+ PARSE_CONFIG,
+ PARSE_PARAM,
+ PARSE_ITEM,
+ PARSE_PARAM_DONE,
+
+ PARSE_ERROR
+ } state, save_state;
+
+ /* list of GbfAmChange with changed project elements */
+ gboolean compute_change_set;
+ GSList *change_set;
+
+ /* hash table to keep track of possibly removed nodes */
+ GHashTable *nodes;
+};
+
+
+/* ----- Script spawning data types and constants ----- */
+
+#define GBF_MKFILE_PARSE SCRIPTS_DIR "/gbf-mkfile-parse"
+
+/* timeout in milliseconds */
+#define SCRIPT_TIMEOUT 30000
+
+/* buffer size tuning parameters */
+#define READ_BUFFER_SIZE 32768
+#define READ_BUFFER_DELTA 16384
+
+typedef struct _GbfMkfileSpawnData GbfMkfileSpawnData;
+typedef struct _GbfMkfileChannel GbfMkfileChannel;
+
+struct _GbfMkfileChannel {
+ GIOChannel *channel;
+ gchar *buffer;
+ gsize size; /* allocated buffer size */
+ gsize length; /* real buffer length/index into buffer */
+ guint tag; /* main loop source tag for this channel's watch */
+};
+
+struct _GbfMkfileSpawnData {
+ GMainLoop *main_loop;
+
+ /* child PID to kill it on timeout */
+ gint child_pid;
+
+ /* buffers and related channels */
+ GbfMkfileChannel input;
+ GbfMkfileChannel output;
+ GbfMkfileChannel error;
+ gint open_channels;
+};
+
+
+/* ----- Standard GObject types and variables ----- */
+
+enum {
+ PROP_0,
+ PROP_PROJECT_DIR
+};
+
+static GbfProject *parent_class;
+
+
+/* ----------------------------------------------------------------------
+ Private prototypes
+ ---------------------------------------------------------------------- */
+
+static gboolean uri_is_equal (const gchar *uri1,
+ const gchar *uri2);
+static gboolean uri_is_parent (const gchar *parent_uri,
+ const gchar *child_uri);
+static gboolean uri_is_local_path (const gchar *uri);
+static gchar *uri_to_path (const gchar *uri);
+static gchar *uri_normalize (const gchar *uri,
+ const gchar *base_uri);
+
+static gboolean xml_write_add_source (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *uri);
+static gboolean xml_write_remove_source (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GNode *g_node);
+static gboolean xml_write_add_target (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *name,
+ const gchar *type);
+static gboolean xml_write_add_group (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *new_group);
+static xmlDocPtr xml_new_change_doc (GbfMkfileProject *project);
+
+static GbfMkfileChange *change_new (GbfMkfileChangeType ch,
+ GbfMkfileNode *node);
+static void change_free (GbfMkfileChange *change);
+static GbfMkfileChange *change_set_find (GSList *change_set,
+ GbfMkfileChangeType ch,
+ GbfMkfileNodeType type);
+static void change_set_destroy (GSList *change_set);
+
+static void error_set (GError **error,
+ gint code,
+ const gchar *message);
+static GError *parse_errors (GbfMkfileProject *project,
+ const gchar *error_buffer);
+static gboolean parse_output_xml (GbfMkfileProject *project,
+ gchar *xml,
+ gint length,
+ GSList **change_set);
+
+static void spawn_shutdown (GbfMkfileSpawnData *data);
+static void spawn_data_destroy (GbfMkfileSpawnData *data);
+static gboolean spawn_write_child (GIOChannel *ioc,
+ GIOCondition condition,
+ gpointer user_data);
+static gboolean spawn_read_output (GIOChannel *ioc,
+ GIOCondition condition,
+ gpointer user_data);
+static gboolean spawn_read_error (GIOChannel *ioc,
+ GIOCondition condition,
+ gpointer user_data);
+static gboolean spawn_kill_child (GbfMkfileSpawnData *data);
+static GbfMkfileSpawnData *spawn_script (gchar **argv,
+ gint timeout,
+ gchar *input,
+ gint input_size,
+ GIOFunc input_cb,
+ GIOFunc output_cb,
+ GIOFunc error_cb);
+
+static gboolean project_reload (GbfMkfileProject *project,
+ GError **err);
+static gboolean project_update (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GSList **change_set,
+ GError **err);
+
+static void gbf_mkfile_node_free (GbfMkfileNode *node);
+static GNode *project_node_new (GbfMkfileNodeType type);
+static void project_node_destroy (GbfMkfileProject *project,
+ GNode *g_node);
+static void project_data_destroy (GbfMkfileProject *project);
+static void project_data_init (GbfMkfileProject *project);
+
+static void gbf_mkfile_project_class_init (GbfMkfileProjectClass *klass);
+static void gbf_mkfile_project_instance_init (GbfMkfileProject *project);
+static void gbf_mkfile_project_dispose (GObject *object);
+static void gbf_mkfile_project_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+/*
+ * URI and path manipulation functions -----------------------------
+ */
+
+static gboolean
+uri_is_equal (const gchar *uri1,
+ const gchar *uri2)
+{
+ gboolean retval = FALSE;
+ GnomeVFSURI *vfs_uri1, *vfs_uri2;
+
+ vfs_uri1 = gnome_vfs_uri_new (uri1);
+ vfs_uri2 = gnome_vfs_uri_new (uri2);
+ if (vfs_uri1 != NULL && vfs_uri2 != NULL)
+ retval = gnome_vfs_uri_equal (vfs_uri1, vfs_uri2);
+ gnome_vfs_uri_unref (vfs_uri1);
+ gnome_vfs_uri_unref (vfs_uri2);
+
+ return retval;
+}
+
+static gboolean
+uri_is_parent (const gchar *parent_uri,
+ const gchar *child_uri)
+{
+ gboolean retval = FALSE;
+ GnomeVFSURI *parent, *child;
+
+ parent = gnome_vfs_uri_new (parent_uri);
+ child = gnome_vfs_uri_new (child_uri);
+ if (parent != NULL && child != NULL)
+ retval = gnome_vfs_uri_is_parent (parent, child, TRUE);
+ gnome_vfs_uri_unref (parent);
+ gnome_vfs_uri_unref (child);
+
+ return retval;
+}
+
+static gboolean
+uri_is_local_path (const gchar *uri)
+{
+ const gchar *p;
+
+ for (p = uri;
+ g_ascii_isalnum (*p) || *p == '+' || *p == '-' || *p == '.';
+ p++)
+ ;
+
+ if (*p == ':')
+ return FALSE;
+ else
+ return TRUE;
+}
+
+static gchar *
+uri_to_path (const gchar *uri)
+{
+ return gnome_vfs_get_local_path_from_uri (uri);
+}
+
+static gchar *
+uri_normalize (const gchar *uri, const gchar *base_uri)
+{
+ gchar *normalized_uri = NULL;
+
+ if (uri_is_local_path (uri)) {
+ gchar *absolute_path;
+
+ absolute_path = gnome_vfs_expand_initial_tilde (uri);
+ if (!g_path_is_absolute (uri)) {
+ gchar *tmp = absolute_path;
+ gchar *base_dir;
+ if (base_uri != NULL)
+ base_dir = uri_to_path (base_uri);
+ else
+ base_dir = g_get_current_dir ();
+
+ absolute_path = g_build_filename (base_dir, tmp, NULL);
+ g_free (base_dir);
+ g_free (tmp);
+ }
+ normalized_uri = gnome_vfs_make_uri_canonical (absolute_path);
+ g_free (absolute_path);
+ } else {
+ GnomeVFSURI *vfs_uri;
+
+ vfs_uri = gnome_vfs_uri_new (uri);
+ /* FIXME: this should probably check that the method is file: */
+ if (gnome_vfs_uri_is_local (vfs_uri))
+ normalized_uri = gnome_vfs_make_uri_canonical (uri);
+ gnome_vfs_uri_unref (vfs_uri);
+ }
+
+ /* strip trailing slash */
+ if (normalized_uri) {
+ gint length = strlen (normalized_uri);
+ gchar *p;
+ if (length > 0) {
+ p = normalized_uri + length - 1;
+ if (*p == '/')
+ *p = '\0';
+ }
+ }
+
+ return normalized_uri;
+}
+
+/**
+ * uri_get_chrooted_path:
+ * @root_uri: the root uri (or NULL)
+ * @uri: the uri which must be inside @root_uri for which the
+ * root-changed path is wanted
+ *
+ * E.g.: uri_get_chrooted_path ("file:///foo/bar", "file:///foo/bar/baz/winkle") -> "/baz/winkle"
+ *
+ * Return value: a newly allocated chrooted path
+ **/
+static gchar *
+uri_get_chrooted_path (const gchar *root_uri, const gchar *uri)
+{
+ gchar *root_path, *path;
+ gint root_length;
+ gchar *retval = NULL;
+
+ path = uri_to_path (uri);
+ if (root_uri == NULL)
+ return path;
+
+ root_path = uri_to_path (root_uri);
+
+ root_length = strlen (root_path);
+ if (strncmp (root_path, path, root_length) == 0) {
+ /* check for trailing path separator in root... we need it to
+ * make the returned path absolute */
+ if (root_path [root_length - 1] == '/')
+ root_length--;
+ retval = g_strdup (path + root_length);
+ }
+
+ g_free (root_path);
+ g_free (path);
+
+ return retval;
+}
+
+/*
+ * Project modification functions -----------------------------
+ */
+
+static xmlNodePtr
+xml_new_source_node (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ const gchar *uri)
+{
+ xmlNodePtr source;
+ gchar *filename;
+
+ source = xmlNewDocNode (doc, NULL, BAD_CAST("source"), NULL);
+ filename = uri_get_chrooted_path (project->project_root_uri, uri);
+ xmlSetProp (source, BAD_CAST("uri"), BAD_CAST(filename));
+ g_free (filename);
+
+ return source;
+}
+
+static xmlNodePtr
+xml_write_location_recursive (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ xmlNodePtr cur,
+ GNode *g_node)
+{
+ xmlNodePtr result, xml_node, last_node;
+ gboolean finished = FALSE;
+
+ result = NULL;
+ last_node = NULL;
+ xml_node = NULL;
+ while (g_node && !finished) {
+ GbfMkfileNode *node = GBF_MKFILE_NODE (g_node);
+ switch (node->type) {
+ case GBF_MKFILE_NODE_GROUP:
+ xml_node = xmlNewDocNode (doc, NULL, BAD_CAST("group"), NULL);
+ xmlSetProp (xml_node, BAD_CAST("id"), BAD_CAST(node->id));
+ finished = TRUE;
+ break;
+ case GBF_MKFILE_NODE_TARGET:
+ xml_node = xmlNewDocNode (doc, NULL, BAD_CAST("target"), NULL);
+ /* strip the group id from the target
+ id, since the script identifies
+ targets only within the group */
+ xmlSetProp (xml_node, BAD_CAST("id"),
+ BAD_CAST(node->id +
+ strlen (GBF_MKFILE_NODE (g_node->parent)->id)));
+ break;
+ case GBF_MKFILE_NODE_SOURCE:
+ xml_node = xml_new_source_node (project, doc, node->uri);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ /* set returned node */
+ if (result == NULL)
+ result = xml_node;
+
+ /* link the previously created node to the new node */
+ if (last_node != NULL)
+ xmlAddChild (xml_node, last_node);
+
+ last_node = xml_node;
+ g_node = g_node->parent;
+ }
+ /* link the last created node to the current node */
+ xmlAddChild (cur, last_node);
+
+ return result;
+}
+
+static gboolean
+xml_write_add_source (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *uri)
+{
+ xmlNodePtr cur, source;
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST("add"), NULL);
+ xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("source"));
+ xmlAddChild (doc->children, cur);
+
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+ source = xml_new_source_node (project, doc, uri);
+ xmlAddChild (cur, source);
+
+ return (cur != NULL);
+}
+
+static gboolean
+xml_write_remove_source (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GNode *g_node)
+{
+ xmlNodePtr cur;
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST("remove"), NULL);
+ xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("source"));
+ xmlAddChild (doc->children, cur);
+
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+
+ return (cur != NULL);
+}
+
+static gboolean
+xml_write_add_target (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *name,
+ const gchar *type)
+{
+ xmlNodePtr cur, target;
+
+ g_assert (GBF_MKFILE_NODE (g_node)->type == GBF_MKFILE_NODE_GROUP);
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST("add"), NULL);
+ xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("target"));
+ xmlAddChild (doc->children, cur);
+
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+
+ target = xmlNewDocNode (doc, NULL, BAD_CAST("target"), NULL);
+ xmlSetProp (target, BAD_CAST("id"), BAD_CAST(name));
+ xmlSetProp (target, BAD_CAST("type"), BAD_CAST(type));
+ xmlAddChild (cur, target);
+
+ return TRUE;
+}
+
+typedef struct {
+ GbfMkfileConfigMapping *old_config;
+ xmlDocPtr doc;
+ xmlNodePtr curr_xml_node;
+} GbfXmlWriteData;
+
+static void
+xml_write_set_item_config_cb (const gchar *param, GbfMkfileConfigValue *value,
+ gpointer user_data)
+{
+ xmlNodePtr param_node;
+ GbfXmlWriteData *data = (GbfXmlWriteData*)user_data;
+
+ if (value->type == GBF_MKFILE_TYPE_STRING) {
+ GbfMkfileConfigValue *old_value;
+ const gchar *old_str = "", *new_str = "";
+
+ new_str = gbf_mkfile_config_value_get_string (value);
+ if (new_str == NULL)
+ new_str = "";
+
+ old_value = gbf_mkfile_config_mapping_lookup (data->old_config, param);
+ if (old_value) {
+ old_str = gbf_mkfile_config_value_get_string (old_value);
+ if (old_str == NULL)
+ old_str = "";
+ }
+ if (strcmp (new_str, old_str) != 0)
+ {
+ param_node = xmlNewDocNode (data->doc, NULL,
+ BAD_CAST("item"), NULL);
+ xmlSetProp (param_node, BAD_CAST("name"), BAD_CAST(param));
+ xmlSetProp (param_node, BAD_CAST("value"), BAD_CAST(new_str));
+ xmlAddChild (data->curr_xml_node, param_node);
+ }
+ }
+}
+
+static void
+xml_write_set_param_config_cb (const gchar *param, GbfMkfileConfigValue *value,
+ gpointer user_data)
+{
+ xmlNodePtr param_node;
+ GbfXmlWriteData *data = (GbfXmlWriteData*)user_data;
+
+ if (value->type == GBF_MKFILE_TYPE_STRING) {
+ GbfMkfileConfigValue *old_value;
+ const gchar *old_str = "", *new_str = "";
+
+ new_str = gbf_mkfile_config_value_get_string (value);
+ if (new_str == NULL)
+ new_str = "";
+
+ old_value = gbf_mkfile_config_mapping_lookup (data->old_config, param);
+ if (old_value) {
+ old_str = gbf_mkfile_config_value_get_string (old_value);
+ if (old_str == NULL)
+ old_str = "";
+ }
+ if (strcmp (new_str, old_str) != 0)
+ {
+ param_node = xmlNewDocNode (data->doc, NULL,
+ BAD_CAST("param"), NULL);
+ xmlSetProp (param_node, BAD_CAST("name"), BAD_CAST(param));
+ xmlSetProp (param_node, BAD_CAST("value"), BAD_CAST(new_str));
+ xmlAddChild (data->curr_xml_node, param_node);
+ }
+ } else if (value->type == GBF_MKFILE_TYPE_LIST) {
+ param_node = xmlNewDocNode (data->doc, NULL,
+ BAD_CAST("param"), NULL);
+ xmlSetProp (param_node, BAD_CAST("name"), BAD_CAST(param));
+ /* FIXME */
+ } else if (value->type == GBF_MKFILE_TYPE_MAPPING) {
+
+ GbfXmlWriteData write_data;
+ GbfMkfileConfigValue *old_value;
+ GbfMkfileConfigMapping *new_mapping, *old_mapping;
+
+ new_mapping = gbf_mkfile_config_value_get_mapping (value);
+ old_value = gbf_mkfile_config_mapping_lookup (data->old_config, param);
+ old_mapping = gbf_mkfile_config_value_get_mapping (old_value);
+
+ param_node = xmlNewDocNode (data->doc, NULL, BAD_CAST("param"), NULL);
+
+ xmlSetProp (param_node, BAD_CAST("name"), BAD_CAST(param));
+
+ write_data.doc = data->doc;
+ write_data.curr_xml_node = param_node;
+ write_data.old_config = old_mapping;
+
+ gbf_mkfile_config_mapping_foreach (new_mapping,
+ xml_write_set_item_config_cb,
+ &write_data);
+ if (param_node->children)
+ xmlAddChild (data->curr_xml_node, param_node);
+ else
+ xmlFreeNode (param_node);
+ } else {
+ g_warning ("Should not be here");
+ }
+}
+
+static gboolean
+xml_write_set_config (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ GbfMkfileConfigMapping *new_config)
+{
+ xmlNodePtr cur, config;
+ GbfXmlWriteData user_data;
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST("set"), NULL);
+ xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("config"));
+ xmlAddChild (doc->children, cur);
+
+ if (g_node)
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+
+ config = xmlNewDocNode (doc, NULL, BAD_CAST("config"), NULL);
+ xmlAddChild (cur, config);
+
+ user_data.doc = doc;
+ user_data.curr_xml_node = config;
+ user_data.old_config = GBF_MKFILE_NODE (g_node)->config;
+
+ gbf_mkfile_config_mapping_foreach (new_config,
+ xml_write_set_param_config_cb,
+ &user_data);
+ if (config->children)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static gboolean
+xml_write_remove_target (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GNode *g_node)
+{
+ xmlNodePtr cur;
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST("remove"), NULL);
+ xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("target"));
+ xmlAddChild (doc->children, cur);
+
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+
+ return (cur != NULL);
+}
+
+static gboolean
+xml_write_add_group (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GNode *g_node,
+ const gchar *new_group)
+{
+ xmlNodePtr cur, group;
+ gchar *new_id;
+
+ g_assert (GBF_MKFILE_NODE (g_node)->type == GBF_MKFILE_NODE_GROUP);
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST("add"), NULL);
+ xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("group"));
+ xmlAddChild (doc->children, cur);
+
+ /* calculate new id needed by the script */
+ new_id = g_strdup_printf ("%s%s/", GBF_MKFILE_NODE (g_node)->id, new_group);
+
+ group = xmlNewDocNode (doc, NULL, BAD_CAST("group"), NULL);
+ xmlSetProp (group, BAD_CAST("id"), BAD_CAST(new_id));
+ xmlAddChild (cur, group);
+ g_free (new_id);
+
+ return TRUE;
+}
+
+static gboolean
+xml_write_remove_group (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GNode *g_node)
+{
+ xmlNodePtr cur;
+
+ cur = xmlNewDocNode (doc, NULL, BAD_CAST("remove"), NULL);
+ xmlSetProp (cur, BAD_CAST("type"), BAD_CAST("group"));
+ xmlAddChild (doc->children, cur);
+
+ cur = xml_write_location_recursive (project, doc, cur, g_node);
+
+ return (cur != NULL);
+}
+
+static xmlDocPtr
+xml_new_change_doc (GbfMkfileProject *project)
+{
+ xmlDocPtr doc;
+
+ doc = xmlNewDoc (BAD_CAST("1.0"));
+ if (doc != NULL) {
+ gchar *root_path;
+ root_path = uri_to_path (project->project_root_uri);
+ doc->children = xmlNewDocNode (doc, NULL, BAD_CAST("project"), NULL);
+ xmlSetProp (doc->children, BAD_CAST("root"), BAD_CAST(root_path));
+ g_free (root_path);
+ }
+
+ return doc;
+}
+
+
+/*
+ * File monitoring support --------------------------------
+ * FIXME: review these
+ */
+
+static void
+monitor_cb (GnomeVFSMonitorHandle *handle,
+ const gchar *monitor_uri,
+ const gchar *info_uri,
+ GnomeVFSMonitorEventType event_type,
+ gpointer data)
+{
+ GbfMkfileProject *project = data;
+
+ g_return_if_fail (project != NULL && GBF_IS_MKFILE_PROJECT (project));
+
+ switch (event_type) {
+ case GNOME_VFS_MONITOR_EVENT_CHANGED:
+ case GNOME_VFS_MONITOR_EVENT_DELETED:
+ /* monitor will be removed here... is this safe? */
+ DEBUG (g_message ("File changed"));
+ project_reload (project, NULL);
+ g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+monitor_add (GbfMkfileProject *project, const gchar *uri)
+{
+ GnomeVFSMonitorHandle *handle = NULL;
+ GnomeVFSResult result;
+
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (project->monitors != NULL);
+
+ if (!uri)
+ return;
+
+ handle = g_hash_table_lookup (project->monitors, uri);
+ if (!handle) {
+ GnomeVFSURI *vfs_uri;
+ gboolean exists;
+
+ vfs_uri = gnome_vfs_uri_new (uri);
+ exists = gnome_vfs_uri_exists (vfs_uri);
+ gnome_vfs_uri_unref (vfs_uri);
+
+ if (exists) {
+ result = gnome_vfs_monitor_add (&handle,
+ uri,
+ GNOME_VFS_MONITOR_FILE,
+ monitor_cb,
+ project);
+ if (result == GNOME_VFS_OK) {
+ g_hash_table_insert (project->monitors,
+ g_strdup (uri),
+ handle);
+ }
+ }
+ }
+}
+
+static void
+monitors_remove (GbfMkfileProject *project)
+{
+ g_return_if_fail (project != NULL);
+
+ if (project->monitors)
+ g_hash_table_destroy (project->monitors);
+ project->monitors = NULL;
+}
+
+static void
+group_hash_foreach_monitor (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GNode *group_node = value;
+ GbfMkfileProject *project = user_data;
+
+ monitor_add (project, GBF_MKFILE_NODE (group_node)->uri);
+}
+
+static void
+monitors_setup (GbfMkfileProject *project)
+{
+ g_return_if_fail (project != NULL);
+
+ monitors_remove (project);
+
+ /* setup monitors hash */
+ project->monitors = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) gnome_vfs_monitor_cancel);
+
+ monitor_add (project, project->project_file);
+ g_hash_table_foreach (project->groups, group_hash_foreach_monitor, project);
+}
+
+
+/*
+ * Change sets functions --------------------------------
+ */
+
+static GbfMkfileChange *
+change_new (GbfMkfileChangeType ch, GbfMkfileNode *node)
+{
+ GbfMkfileChange *change;
+
+ change = g_new0 (GbfMkfileChange, 1);
+ change->change = ch;
+ change->type = node->type;
+ change->id = g_strdup (node->id);
+
+ return change;
+}
+
+static void
+change_free (GbfMkfileChange *change)
+{
+ if (change) {
+ g_free (change->id);
+ g_free (change);
+ }
+}
+
+static GbfMkfileChange *
+change_set_find (GSList *change_set, GbfMkfileChangeType ch, GbfMkfileNodeType type)
+{
+ GSList *iter = change_set;
+
+ while (iter) {
+ GbfMkfileChange *change = iter->data;
+ if (change->change == ch && change->type == type)
+ return change;
+ iter = g_slist_next (iter);
+ }
+
+ return NULL;
+}
+
+static void
+change_set_destroy (GSList *change_set)
+{
+ GSList *l;
+ for (l = change_set; l; l = g_slist_next (l))
+ change_free (l->data);
+ g_slist_free (change_set);
+}
+
+#ifdef ENABLE_DEBUG
+static void
+change_set_debug_print (GSList *change_set)
+{
+ GSList *iter = change_set;
+
+ g_print ("Change set:\n");
+ while (iter) {
+ GbfMkfileChange *change = iter->data;
+
+ switch (change->change) {
+ case GBF_MKFILE_CHANGE_ADDED:
+ g_print ("added ");
+ break;
+ case GBF_MKFILE_CHANGE_REMOVED:
+ g_print ("removed ");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ switch (change->type) {
+ case GBF_MKFILE_NODE_GROUP:
+ g_print ("group ");
+ break;
+ case GBF_MKFILE_NODE_TARGET:
+ g_print ("target ");
+ break;
+ case GBF_MKFILE_NODE_SOURCE:
+ g_print ("source ");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ g_print ("%s\n", change->id);
+
+ iter = g_slist_next (iter);
+ }
+}
+#endif
+
+/*
+ * Perl script output parser ------------------------------
+ */
+
+#define PARSER_ASSERT(x) G_STMT_START { \
+ if (!(x)) { \
+ DEBUG (g_warning ("Parser assertion at " G_STRLOC " failed: " #x)); \
+ data->state = PARSE_ERROR; return; \
+ } \
+ \
+ } G_STMT_END
+
+static void
+sax_start_element (void *ctxt, const xmlChar *name, const xmlChar **attrs)
+{
+ GbfMkfileProjectParseData *data;
+ GbfMkfileProject *project;
+ GNode *g_node;
+ GbfMkfileNode *node;
+
+ data = ctxt;
+ project = data->project;
+
+ PARSER_ASSERT (data->state != PARSE_ERROR && data->state != PARSE_DONE);
+
+ if (xmlStrEqual (name, BAD_CAST "project")) {
+ const xmlChar *project_file = NULL;
+
+ /* project node */
+ PARSER_ASSERT (data->state == PARSE_INITIAL);
+ data->state = PARSE_PROJECT;
+
+ /* process attributes: lookup project file and check
+ * if we're getting the whole project or just changed
+ * groups */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "root")) {
+ /* FIXME: check that the root is the
+ * same as the project's root */
+ }
+ else if (xmlStrEqual (*attrs, BAD_CAST "report"))
+ data->full_report = (xmlStrEqual (*val, BAD_CAST "full") != 0);
+ else if (xmlStrEqual (*attrs, BAD_CAST "source"))
+ project_file = *val;
+
+ attrs = ++val;
+ }
+
+ /* clear project if we are getting a full report */
+ if (data->full_report) {
+ /* FIXME: will this be correct in all cases? */
+ data->compute_change_set = FALSE;
+ project_data_init (project);
+ }
+
+ /* assign here, since we destroy the data in project_data_init() */
+ if (project_file) {
+ g_free (project->project_file);
+ project->project_file = g_strdup ((gchar*)project_file);
+ }
+
+ } else if (xmlStrEqual (name, BAD_CAST "group")) {
+ const xmlChar *group_name = NULL;
+ const xmlChar *group_id = NULL;
+ const xmlChar *group_source = NULL;
+
+ /* group node */
+ PARSER_ASSERT (data->state == PARSE_PROJECT ||
+ data->state == PARSE_GROUP);
+ data->state = PARSE_GROUP;
+
+ /* process attributes: lookup name, id and source file
+ * (i.e. Makefile) */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "name"))
+ group_name = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "id"))
+ group_id = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "source"))
+ group_source = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (group_name != NULL && group_id != NULL);
+
+ /* lookup the node first, as it could already exist */
+ g_node = g_hash_table_lookup (project->groups, group_id);
+ if (g_node == NULL) {
+ g_node = project_node_new (GBF_MKFILE_NODE_GROUP);
+ node = GBF_MKFILE_NODE (g_node);
+
+ node->id = g_strdup ((gchar*)group_id);
+
+ /* set parent group */
+ if (data->current_node != NULL) {
+ g_node_prepend (data->current_node, g_node);
+ } else {
+ /* node is project root */
+ g_assert (project->root_node == NULL);
+ project->root_node = g_node;
+ }
+
+ if (data->compute_change_set)
+ data->change_set = g_slist_prepend (
+ data->change_set,
+ change_new (GBF_MKFILE_CHANGE_ADDED, node));
+
+ /* save the group in the hash for quick access */
+ g_hash_table_insert (project->groups, g_strdup (node->id), g_node);
+
+ } else {
+ GNode *child;
+
+ node = GBF_MKFILE_NODE (g_node);
+
+ /* node found so remove it from the tracking hash */
+ g_hash_table_remove (data->nodes, g_node);
+
+ /* re-set data */
+ g_free (node->name);
+ g_free (node->uri);
+
+ /* recreate the configuration */
+ gbf_mkfile_config_mapping_destroy (node->config);
+
+ /* track all child nodes */
+ child = g_node_first_child (g_node);
+ while (child != NULL) {
+ /* only track groups if getting a full
+ * report, otherwise non-changed
+ * groups are later removed from the
+ * project because the script doesn't
+ * report them */
+ if (data->full_report ||
+ GBF_MKFILE_NODE (child)->type != GBF_MKFILE_NODE_GROUP)
+ g_hash_table_insert (data->nodes, child, child);
+ child = g_node_next_sibling (child);
+ }
+ }
+ node->name = g_strdup ((gchar*)group_name);
+ node->uri = g_strdup ((gchar*)group_source);
+ node->config = gbf_mkfile_config_mapping_new ();
+
+ /* set working node */
+ data->depth++;
+ data->current_node = g_node;
+
+ } else if (xmlStrEqual (name, BAD_CAST "target")) {
+ const xmlChar *id = NULL;
+ const xmlChar *target_name = NULL;
+ const xmlChar *target_type = NULL;
+ gchar *group_id;
+
+ /* target node */
+ PARSER_ASSERT (data->state == PARSE_GROUP);
+ data->state = PARSE_TARGET;
+
+ /* process attributes: lookup name, type and id */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "name"))
+ target_name = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "type"))
+ target_type = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "id"))
+ id = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (target_name != NULL && target_type != NULL);
+
+ /* compute the id using the given by the script if possible */
+ group_id = g_strdup_printf ("%s%s",
+ GBF_MKFILE_NODE (data->current_node)->id,
+ id != NULL ? id : target_name);
+
+ /* lookup the node first, as it could already exist */
+ g_node = g_hash_table_lookup (project->targets, group_id);
+ if (g_node == NULL) {
+ g_node = project_node_new (GBF_MKFILE_NODE_TARGET);
+ node = GBF_MKFILE_NODE (g_node);
+
+ node->id = group_id;
+
+ /* set target's parent */
+ g_node_prepend (data->current_node, g_node);
+
+ if (data->compute_change_set)
+ data->change_set = g_slist_prepend (
+ data->change_set,
+ change_new (GBF_MKFILE_CHANGE_ADDED, node));
+
+ /* save the target in the hash for quick access */
+ g_hash_table_insert (project->targets, g_strdup (node->id), g_node);
+
+ } else {
+ GNode *child;
+
+ node = GBF_MKFILE_NODE (g_node);
+ g_free (group_id);
+
+ /* node found so remove it from the tracking hash */
+ g_hash_table_remove (data->nodes, g_node);
+
+ /* re-set data */
+ g_free (node->name);
+ g_free (node->detail);
+
+ /* recreate the configuration */
+ gbf_mkfile_config_mapping_destroy (node->config);
+
+ /* track all child nodes */
+ child = g_node_first_child (g_node);
+ while (child != NULL) {
+ g_hash_table_insert (data->nodes, child, child);
+ child = g_node_next_sibling (child);
+ }
+ }
+ node->name = g_strdup ((gchar*)target_name);
+ node->detail = g_strdup ((gchar*)target_type);
+ node->config = gbf_mkfile_config_mapping_new ();
+
+ /* set working node */
+ data->current_node = g_node;
+
+ } else if (xmlStrEqual (name, BAD_CAST "source")) {
+ const xmlChar *uri = NULL;
+ gchar *source_uri, *source_id;
+
+ /* source node */
+ PARSER_ASSERT (data->state == PARSE_TARGET);
+ data->state = PARSE_SOURCE;
+
+ /* process attributes: lookup uri */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "uri"))
+ uri = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (uri != NULL);
+
+ source_uri = g_build_filename (project->project_root_uri, uri, NULL);
+ source_id = g_strdup_printf ("%s:%s",
+ GBF_MKFILE_NODE (data->current_node)->id,
+ source_uri);
+
+ /* lookup the node first, as it could already exist */
+ g_node = g_hash_table_lookup (project->sources, source_id);
+ if (g_node == NULL) {
+ g_node = project_node_new (GBF_MKFILE_NODE_SOURCE);
+ node = GBF_MKFILE_NODE (g_node);
+
+ node->id = source_id;
+
+ /* set source's parent */
+ g_node_prepend (data->current_node, g_node);
+
+ if (data->compute_change_set)
+ data->change_set = g_slist_prepend (
+ data->change_set,
+ change_new (GBF_MKFILE_CHANGE_ADDED, node));
+
+ /* save the source in the hash for quick access */
+ g_hash_table_insert (project->sources, g_strdup (node->id), g_node);
+
+ } else {
+ /* node found so remove it from the tracking hash */
+ g_hash_table_remove (data->nodes, g_node);
+
+ node = GBF_MKFILE_NODE (g_node);
+ g_free (source_id);
+
+ /* re-set data */
+ g_free (node->uri);
+ }
+ node->uri = source_uri;
+
+ /* set working node */
+ data->current_node = g_node;
+
+ } else if (xmlStrEqual (name, BAD_CAST "dependency")) {
+ const xmlChar *uri = NULL;
+ const xmlChar *target_dep = NULL;
+ gchar *source_uri, *source_id;
+
+ /* dependency node */
+ PARSER_ASSERT (data->state == PARSE_TARGET);
+ data->state = PARSE_DEPENDENCY;
+
+ /* process attributes: lookup file and dependency target */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "file"))
+ uri = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "target"))
+ target_dep = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (uri != NULL && target_dep != NULL);
+
+ /* Compute the id */
+ source_uri = g_build_filename (project->project_root_uri, uri, NULL);
+ source_id = g_strdup_printf ("%s:%s",
+ GBF_MKFILE_NODE (data->current_node)->id,
+ source_uri);
+
+ /* lookup the node first, as it could already exist */
+ g_node = g_hash_table_lookup (project->sources, source_id);
+ if (g_node == NULL) {
+ g_node = project_node_new (GBF_MKFILE_NODE_SOURCE);
+ node = GBF_MKFILE_NODE (g_node);
+
+ node->id = source_id;
+
+ /* set source's parent */
+ g_node_prepend (data->current_node, g_node);
+
+ if (data->compute_change_set)
+ data->change_set = g_slist_prepend (
+ data->change_set,
+ change_new (GBF_MKFILE_CHANGE_ADDED, node));
+
+ /* save the source in the hash for quick access */
+ g_hash_table_insert (project->sources, g_strdup (node->id), g_node);
+
+ } else {
+ /* node found so remove it from the tracking hash */
+ g_hash_table_remove (data->nodes, g_node);
+
+ g_free (source_id);
+ node = GBF_MKFILE_NODE (g_node);
+
+ /* re-set data */
+ g_free (node->uri);
+ g_free (node->detail);
+ }
+ node->uri = source_uri;
+ node->detail = g_strdup ((gchar*)target_dep);
+
+ /* set working node */
+ data->current_node = g_node;
+
+ } else if (xmlStrEqual (name, BAD_CAST "config")) {
+ /* config node */
+ PARSER_ASSERT (data->state == PARSE_PROJECT ||
+ data->state == PARSE_GROUP ||
+ data->state == PARSE_TARGET);
+
+ switch (data->state) {
+ case PARSE_PROJECT:
+ data->config = project->project_config;
+ break;
+ case PARSE_GROUP:
+ case PARSE_TARGET:
+ g_assert (data->current_node != NULL);
+ data->config = GBF_MKFILE_NODE (data->current_node)->config;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ data->save_state = data->state;
+ data->state = PARSE_CONFIG;
+
+ } else if (xmlStrEqual (name, BAD_CAST "param")) {
+ const xmlChar *param_name = NULL;
+ const xmlChar *param_value = NULL;
+
+ /* config param node */
+ PARSER_ASSERT (data->state == PARSE_CONFIG);
+
+ /* process attributes: lookup parameter name and value (if scalar) */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "name"))
+ param_name = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "value"))
+ param_value = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (param_name != NULL);
+
+ if (param_value != NULL) {
+ GbfMkfileConfigValue *param;
+
+ /* save scalar string parameter */
+ param = gbf_mkfile_config_value_new (GBF_MKFILE_TYPE_STRING);
+ gbf_mkfile_config_value_set_string (param, (gchar*)param_value);
+
+ /* try to save the parameter in the config mapping */
+ if (!gbf_mkfile_config_mapping_insert (data->config,
+ (gchar*)param_name, param)) {
+ gbf_mkfile_config_value_free (param);
+ }
+
+ /* we are finished with the parameter */
+ data->state = PARSE_PARAM_DONE;
+
+ } else {
+ /* we expect a list or a hash of values */
+ data->param_key = g_strdup ((gchar*)param_name);
+ data->state = PARSE_PARAM;
+ }
+
+ } else if (xmlStrEqual (name, BAD_CAST "item")) {
+ GbfMkfileConfigValue *param, *item;
+ const xmlChar *item_name = NULL;
+ const xmlChar *item_value = NULL;
+
+ /* parameter item node */
+ PARSER_ASSERT (data->state == PARSE_PARAM);
+ g_assert (data->param_key != NULL);
+ data->state = PARSE_ITEM;
+
+ /* process attributes: lookup parameter name and value (if scalar) */
+ while (attrs && *attrs != NULL) {
+ const xmlChar **val = attrs;
+
+ val++;
+ if (xmlStrEqual (*attrs, BAD_CAST "name"))
+ item_name = *val;
+ else if (xmlStrEqual (*attrs, BAD_CAST "value"))
+ item_value = *val;
+
+ attrs = ++val;
+ }
+ PARSER_ASSERT (item_value != NULL);
+
+ /* create item value */
+ item = gbf_mkfile_config_value_new (GBF_MKFILE_TYPE_STRING);
+ gbf_mkfile_config_value_set_string (item, (gchar*)item_value);
+
+ /* get current configuration parameter */
+ param = gbf_mkfile_config_mapping_lookup (data->config, data->param_key);
+
+ /* if this is the first item, we must decide whether
+ it's a list or a mapping. if it's not the first
+ one, we must check that the items are of the same
+ type */
+ if (param == NULL) {
+ if (item_name != NULL) {
+ param = gbf_mkfile_config_value_new (GBF_MKFILE_TYPE_MAPPING);
+ } else {
+ param = gbf_mkfile_config_value_new (GBF_MKFILE_TYPE_LIST);
+ }
+ gbf_mkfile_config_mapping_insert (data->config,
+ data->param_key,
+ param);
+ }
+
+ switch (param->type) {
+ case GBF_MKFILE_TYPE_LIST:
+ param->list = g_slist_prepend (param->list, item);
+ break;
+
+ case GBF_MKFILE_TYPE_MAPPING:
+ if (item_name == NULL ||
+ !gbf_mkfile_config_mapping_insert (param->mapping,
+ (gchar*)item_name,
+ item)) {
+ gbf_mkfile_config_value_free (item);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+}
+
+static void
+sax_end_element (void *ctx, const xmlChar *name)
+{
+ GbfMkfileProjectParseData *data = ctx;
+
+ PARSER_ASSERT (data->state != PARSE_ERROR && data->state != PARSE_DONE);
+
+ if (xmlStrEqual (name, BAD_CAST "project")) {
+ PARSER_ASSERT (data->state == PARSE_PROJECT);
+ g_assert (data->current_node == NULL);
+ data->state = PARSE_DONE;
+
+ } else if (xmlStrEqual (name, BAD_CAST "group")) {
+ PARSER_ASSERT (data->state == PARSE_GROUP);
+ g_assert (data->current_node != NULL);
+ data->depth--;
+ if (data->depth == 0) {
+ data->current_node = NULL;
+ data->state = PARSE_PROJECT;
+ } else {
+ data->current_node = data->current_node->parent;
+ }
+
+ /* FIXME: resolve all target inter-dependencies here */
+
+ } else if (xmlStrEqual (name, BAD_CAST "target")) {
+ PARSER_ASSERT (data->state == PARSE_TARGET);
+ g_assert (data->current_node != NULL);
+ data->current_node = data->current_node->parent;
+ data->state = PARSE_GROUP;
+
+ } else if (xmlStrEqual (name, BAD_CAST "source")) {
+ PARSER_ASSERT (data->state == PARSE_SOURCE);
+ g_assert (data->current_node != NULL);
+ data->current_node = data->current_node->parent;
+ data->state = PARSE_TARGET;
+
+ } else if (xmlStrEqual (name, BAD_CAST "dependency")) {
+ PARSER_ASSERT (data->state == PARSE_DEPENDENCY);
+ g_assert (data->current_node != NULL);
+ data->current_node = data->current_node->parent;
+ data->state = PARSE_TARGET;
+
+ } else if (xmlStrEqual (name, BAD_CAST "config")) {
+ PARSER_ASSERT (data->state == PARSE_CONFIG);
+ data->state = data->save_state;
+ data->save_state = PARSE_INITIAL;
+ data->config = NULL;
+
+ } else if (xmlStrEqual (name, BAD_CAST "param")) {
+ PARSER_ASSERT (data->state == PARSE_PARAM ||
+ data->state == PARSE_PARAM_DONE);
+ data->state = PARSE_CONFIG;
+ g_free (data->param_key);
+ data->param_key = NULL;
+
+ } else if (xmlStrEqual (name, BAD_CAST "item")) {
+ PARSER_ASSERT (data->state == PARSE_ITEM);
+ data->state = PARSE_PARAM;
+
+ }
+}
+
+#undef PARSER_ASSERT
+
+static void
+hash_foreach_add_removed (GNode *g_node,
+ gpointer value,
+ GSList **change_set)
+{
+ *change_set = g_slist_prepend (*change_set,
+ change_new (GBF_MKFILE_CHANGE_REMOVED,
+ GBF_MKFILE_NODE (g_node)));
+}
+
+static void
+hash_foreach_destroy_node (GNode *g_node,
+ gpointer value,
+ GbfMkfileProject *project)
+{
+ project_node_destroy (project, g_node);
+}
+
+static gboolean
+parse_output_xml (GbfMkfileProject *project,
+ gchar *xml_text,
+ gint length,
+ GSList **change_set)
+{
+ xmlSAXHandler handler;
+ GbfMkfileProjectParseData data;
+ int retval;
+
+ memset (&handler, 0, sizeof (xmlSAXHandler));
+ handler.startElement = sax_start_element;
+ handler.endElement = sax_end_element;
+ handler.initialized = 0;
+
+ data.state = PARSE_INITIAL;
+ data.save_state = PARSE_INITIAL;
+ data.project = project;
+ data.current_node = NULL;
+ data.depth = 0;
+ data.config = NULL;
+ data.param_key = NULL;
+ data.full_report = TRUE;
+
+ data.change_set = NULL;
+ data.nodes = g_hash_table_new (g_direct_hash, g_direct_equal);
+ data.compute_change_set = (change_set != NULL);
+
+ xmlSubstituteEntitiesDefault (TRUE);
+
+ /* Parse document */
+ retval = xmlSAXUserParseMemory (&handler, &data, xml_text, length);
+
+ if (data.state != PARSE_DONE)
+ retval = -1;
+
+ /* construct the change set */
+ if (retval >= 0 && data.compute_change_set) {
+ g_hash_table_foreach (data.nodes,
+ (GHFunc) hash_foreach_add_removed,
+ &data.change_set);
+ *change_set = data.change_set;
+ data.change_set = NULL;
+ }
+
+ /* free up any remaining parsing data */
+ change_set_destroy (data.change_set);
+ if (data.nodes) {
+ g_hash_table_foreach (data.nodes,
+ (GHFunc) hash_foreach_destroy_node,
+ project);
+ g_hash_table_destroy (data.nodes);
+ }
+ g_free (data.param_key);
+
+ return (retval >= 0);
+}
+
+
+/*
+ * Perl script error parsing ------------------------
+ */
+
+/* FIXME: set correct error codes in all the places we call this function */
+static void
+error_set (GError **error, gint code, const gchar *message)
+{
+ if (error != NULL) {
+ if (*error != NULL) {
+ gchar *tmp;
+
+ /* error already created, just change the code
+ * and prepend the string */
+ (*error)->code = code;
+ tmp = (*error)->message;
+ (*error)->message = g_strconcat (message, "\n\n", tmp, NULL);
+ g_free (tmp);
+
+ } else {
+ *error = g_error_new_literal (GBF_PROJECT_ERROR,
+ code,
+ message);
+ }
+ }
+}
+
+static GError *
+parse_errors (GbfMkfileProject *project,
+ const gchar *error_buffer)
+{
+ const gchar *line_ptr, *next_line, *p;
+ GError *err = NULL;
+ GString *message;
+
+ message = g_string_new (NULL);
+ line_ptr = error_buffer;
+ while (line_ptr) {
+ /* FIXME: maybe use constants or enums for the error type */
+ gint error_type = 0;
+ gint error_code;
+ gint line_length;
+
+ next_line = g_strstr_len (line_ptr, strlen (line_ptr), "\n");
+ /* skip newline */
+ next_line = next_line ? next_line + 1 : NULL;
+ line_length = next_line ? next_line - line_ptr : strlen (line_ptr);
+ p = line_ptr;
+
+ if (g_str_has_prefix (line_ptr, ERROR_PREFIX)) {
+ /* get the error */
+ p = line_ptr + strlen (ERROR_PREFIX);
+ error_type = 1;
+ }
+#if 0
+ /* FIXME: skip warnings for now */
+ else if (g_str_has_prefix (line_ptr, WARNING_PREFIX)) {
+ /* get the warning */
+ p = line_ptr + strlen (WARNING_PREFIX);
+ error_type = 2;
+ }
+#endif
+
+ if (error_type != 0) {
+ /* get the error/warning code */
+ error_code = strtol (p, (char **) &p, 10);
+ if (error_code != 0) {
+ p = g_strstr_len (p, line_length, MESSAGE_DELIMITER);
+ if (p != NULL) {
+ gchar *msg;
+
+ /* FIXME: think another
+ * solution for getting the
+ * messages, since this has
+ * problems with i18n as it's
+ * very difficult to get
+ * translated strings from the
+ * perl script */
+ p += strlen (MESSAGE_DELIMITER);
+ if (next_line != NULL)
+ msg = g_strndup (p, next_line - p - 1);
+ else
+ msg = g_strdup (p);
+
+ if (message->len > 0)
+ g_string_append (message, "\n");
+ g_string_append (message, msg);
+ g_free (msg);
+ }
+ }
+ }
+
+ line_ptr = next_line;
+ }
+
+ if (message->len > 0) {
+ err = g_error_new (GBF_PROJECT_ERROR,
+ GBF_PROJECT_ERROR_GENERAL_FAILURE,
+ "%s", message->str);
+ }
+ g_string_free (message, TRUE);
+
+ return err;
+}
+
+
+/*
+ * Process spawning ------------------------
+ */
+
+static void
+shutdown_channel (GbfMkfileSpawnData *data, GbfMkfileChannel *channel)
+{
+ if (channel->channel) {
+ g_io_channel_shutdown (channel->channel, TRUE, NULL);
+ g_io_channel_unref (channel->channel);
+ channel->channel = NULL;
+ }
+ if (channel->tag != 0) {
+ GMainContext *context = NULL;
+ GSource *source;
+ if (data->main_loop != NULL)
+ context = g_main_loop_get_context (data->main_loop);
+ source = g_main_context_find_source_by_id (context, channel->tag);
+ if (source != NULL)
+ g_source_destroy (source);
+ channel->tag = 0;
+ }
+}
+
+static void
+spawn_shutdown (GbfMkfileSpawnData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ if (data->child_pid) {
+ DEBUG (g_message ("Killing child"));
+ kill (data->child_pid, SIGKILL);
+ data->child_pid = 0;
+ }
+
+ /* close channels and remove sources */
+ shutdown_channel (data, &data->input);
+ shutdown_channel (data, &data->output);
+ shutdown_channel (data, &data->error);
+ data->open_channels = 0;
+
+ if (data->output.buffer) {
+ /* shrink buffer and add terminator */
+ data->output.buffer = g_realloc (data->output.buffer,
+ ++data->output.length);
+ data->output.buffer [data->output.length - 1] = 0;
+ }
+
+ if (data->error.buffer) {
+ /* shrink buffer and add terminator */
+ data->error.buffer = g_realloc (data->error.buffer,
+ ++data->error.length);
+ data->error.buffer [data->error.length - 1] = 0;
+ }
+
+ if (data->main_loop)
+ g_main_loop_quit (data->main_loop);
+}
+
+static void
+spawn_data_destroy (GbfMkfileSpawnData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ spawn_shutdown (data);
+
+ if (data->input.buffer) {
+ /* input buffer is provided by the user, so it's not freed here */
+ data->input.buffer = NULL;
+ data->input.size = 0;
+ data->input.length = 0;
+ }
+ if (data->output.buffer) {
+ g_free (data->output.buffer);
+ data->output.buffer = NULL;
+ data->output.size = 0;
+ data->output.length = 0;
+ }
+ if (data->error.buffer) {
+ g_free (data->error.buffer);
+ data->error.buffer = NULL;
+ data->error.size = 0;
+ data->error.length = 0;
+ }
+ g_free (data);
+}
+
+static gboolean
+spawn_write_child (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+ GbfMkfileSpawnData *data = user_data;
+ gboolean retval = FALSE;
+
+ g_assert (data != NULL);
+ g_assert (data->input.channel == ioc);
+
+ if (condition & G_IO_OUT) {
+ gsize bytes_written = 0;
+ GIOStatus status;
+ GError *error = NULL;
+
+ /* try to write all data remaining */
+ status = g_io_channel_write_chars (ioc,
+ data->input.buffer + data->input.length,
+ data->input.size - data->input.length,
+ &bytes_written, &error);
+ data->input.length += bytes_written;
+
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ if (data->input.length < data->input.size) {
+ /* don't remove the source */
+ retval = TRUE;
+ }
+ break;
+
+ default:
+ if (error) {
+ g_warning ("Error while writing to stdin: %s",
+ error->message);
+ g_error_free (error);
+ }
+ break;
+ }
+ }
+
+ if (!retval) {
+ /* finished writing or some error ocurred */
+ g_io_channel_shutdown (data->input.channel, TRUE, NULL);
+ g_io_channel_unref (data->input.channel);
+ data->input.channel = NULL;
+ /* returning false will remove the source */
+ data->input.tag = 0;
+
+ data->open_channels--;
+ if (data->open_channels == 0) {
+ /* need to signal the end of the operation */
+ if (data->main_loop)
+ /* executing synchronously */
+ g_main_loop_quit (data->main_loop);
+ /* FIXME: what to do in the async case? */
+ }
+ }
+
+ return retval;
+}
+
+static gboolean
+read_channel (GbfMkfileChannel *channel, GIOCondition condition, GbfMkfileSpawnData *data)
+{
+ gboolean retval = FALSE;
+
+ if (condition & (G_IO_IN | G_IO_PRI)) {
+ gsize bytes_read = 0;
+ GIOStatus status;
+ GError *error = NULL;
+
+ /* allocate buffer */
+ if (channel->buffer == NULL) {
+ channel->size = READ_BUFFER_SIZE;
+ channel->buffer = g_malloc (channel->size);
+ channel->length = 0;
+ }
+
+ status = g_io_channel_read_chars (channel->channel,
+ channel->buffer + channel->length,
+ channel->size - channel->length,
+ &bytes_read, &error);
+ channel->length += bytes_read;
+
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ /* grow buffer if necessary */
+ if (channel->size - channel->length < READ_BUFFER_DELTA) {
+ channel->size += READ_BUFFER_DELTA;
+ channel->buffer = g_realloc (channel->buffer,
+ channel->size);
+ }
+ retval = TRUE;
+ break;
+
+ case G_IO_STATUS_EOF:
+ /* will close the channel */
+ break;
+
+ default:
+ if (error) {
+ g_warning ("Error while reading stderr: %s",
+ error->message);
+ g_error_free (error);
+ }
+ break;
+ }
+ }
+
+ if (!retval) {
+ /* eof was reached or some error ocurred */
+ g_io_channel_shutdown (channel->channel, FALSE, NULL);
+ g_io_channel_unref (channel->channel);
+ channel->channel = NULL;
+ /* returning false will remove the source */
+ channel->tag = 0;
+
+ data->open_channels--;
+ if (data->open_channels == 0) {
+ /* need to signal the end of the operation */
+ if (data->main_loop)
+ /* executing synchronously */
+ g_main_loop_quit (data->main_loop);
+ /* FIXME: what to do in the async case? */
+ }
+ }
+
+ return retval;
+}
+
+static gboolean
+spawn_read_output (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+ GbfMkfileSpawnData *data = user_data;
+
+ /* some checks first */
+ g_assert (data != NULL);
+ g_assert (ioc == data->output.channel);
+
+ return read_channel (&data->output, condition, data);
+}
+
+static gboolean
+spawn_read_error (GIOChannel *ioc, GIOCondition condition, gpointer user_data)
+{
+ GbfMkfileSpawnData *data = user_data;
+
+ /* some checks first */
+ g_assert (data != NULL);
+ g_assert (ioc == data->error.channel);
+
+ return read_channel (&data->error, condition, data);
+}
+
+static gboolean
+spawn_kill_child (GbfMkfileSpawnData *data)
+{
+ /* we can't wait longer */
+ DEBUG (g_message ("Timeout: sending SIGTERM to child process"));
+
+ kill (data->child_pid, SIGTERM);
+
+ if (data->main_loop)
+ g_main_loop_quit (data->main_loop);
+
+ return FALSE;
+}
+
+static guint
+context_io_add_watch (GMainContext *context,
+ GIOChannel *channel,
+ GIOCondition condition,
+ GSourceFunc func,
+ gpointer user_data)
+{
+ GSource *source;
+ guint id;
+
+ g_return_val_if_fail (channel != NULL, 0);
+
+ source = g_io_create_watch (channel, condition);
+ g_source_set_callback (source, func, user_data, NULL);
+ id = g_source_attach (source, context);
+ g_source_unref (source);
+
+ return id;
+}
+
+static GbfMkfileSpawnData *
+spawn_script (gchar **argv,
+ gint timeout,
+ gchar *input,
+ gint input_size,
+ GIOFunc input_cb,
+ GIOFunc output_cb,
+ GIOFunc error_cb)
+{
+ GbfMkfileSpawnData *data;
+ gint child_in, child_out, child_err;
+ GError *error = NULL;
+ gboolean async;
+
+ data = g_new0 (GbfMkfileSpawnData, 1);
+
+ /* we consider timeout < 0 to mean asynchronous request */
+ async = (timeout <= 0);
+
+ /* setup default callbacks */
+ if (input_cb == NULL) input_cb = spawn_write_child;
+ if (output_cb == NULL) output_cb = spawn_read_output;
+ if (error_cb == NULL) error_cb = spawn_read_error;
+
+ /* set input buffer */
+ if (input) {
+ data->input.buffer = input;
+ data->input.size = input_size;
+ data->input.length = 0; /* for input buffer length acts as an index */
+ }
+
+ DEBUG (g_message ("Spawning script"));
+
+ if (!g_spawn_async_with_pipes (NULL, /* working dir */
+ argv,
+ NULL, /* environment */
+ 0, /* flags */
+ NULL, NULL, /* child setup func */
+ &data->child_pid,
+ &child_in, &child_out, &child_err,
+ &error)) {
+ g_warning ("Unable to fork: %s", error->message);
+ g_error_free (error);
+ g_free (data);
+ return NULL;
+
+ } else {
+ GMainContext *context = NULL;
+
+ if (!async) {
+ /* we need a new context to do the i/o
+ * otherwise we could have re-entrancy
+ * problems since gtk events will be processed
+ * while we iterate the inner main loop */
+ context = g_main_context_new ();
+ data->main_loop = g_main_loop_new (context, FALSE);
+ }
+
+ fcntl (child_in, F_SETFL, O_NONBLOCK);
+ fcntl (child_out, F_SETFL, O_NONBLOCK);
+ fcntl (child_err, F_SETFL, O_NONBLOCK);
+
+ data->open_channels = 3;
+ if (input != NULL && input_size > 0) {
+ data->input.channel = g_io_channel_unix_new (child_in);
+ data->input.tag = context_io_add_watch (context,
+ data->input.channel,
+ G_IO_OUT | G_IO_ERR |
+ G_IO_HUP | G_IO_NVAL,
+ (GSourceFunc) input_cb, data);
+ } else {
+ /* we are not interested in stdin */
+ close (child_in);
+ data->open_channels--;
+ }
+
+ /* create watches for stdout and stderr */
+ data->output.channel = g_io_channel_unix_new (child_out);
+ data->output.tag = context_io_add_watch (context,
+ data->output.channel,
+ G_IO_ERR | G_IO_HUP |
+ G_IO_NVAL | G_IO_IN,
+ (GSourceFunc) output_cb, data);
+ data->error.channel = g_io_channel_unix_new (child_err);
+ data->error.tag = context_io_add_watch (context,
+ data->error.channel,
+ G_IO_ERR | G_IO_HUP |
+ G_IO_NVAL | G_IO_IN,
+ (GSourceFunc) error_cb, data);
+
+ if (!async) {
+ GSource *source;
+
+ /* add the timeout */
+ source = g_timeout_source_new (timeout);
+ g_source_set_callback (source, (GSourceFunc) spawn_kill_child, data, NULL);
+ g_source_attach (source, context);
+ g_source_unref (source);
+
+ g_main_loop_run (data->main_loop);
+
+ /* continue iterations until all channels have been closed */
+ while (data->open_channels > 0 && g_main_context_pending (context))
+ g_main_context_iteration (context, FALSE);
+
+ /* close channels and remove io watches */
+ if (data->open_channels == 0)
+ /* normal shutdown */
+ data->child_pid = 0;
+ spawn_shutdown (data);
+
+ /* destroy main loop & context */
+ g_main_loop_unref (data->main_loop);
+ data->main_loop = NULL;
+ g_main_context_unref (context);
+ }
+
+ return data;
+ }
+}
+
+/*
+ * Script execution control ----------------------------
+ */
+
+static gboolean
+project_reload (GbfMkfileProject *project, GError **err)
+{
+ GbfMkfileSpawnData *data;
+ gchar *argv [5], *project_path;
+ gboolean retval;
+ gint i;
+
+ project_path = uri_to_path (project->project_root_uri);
+
+ i = 0;
+ argv [i++] = GBF_MKFILE_PARSE;
+ DEBUG (argv [i++] = "-d");
+ argv [i++] = "--get";
+ argv [i++] = project_path;
+ argv [i++] = NULL;
+ g_assert (i <= G_N_ELEMENTS (argv));
+
+ data = spawn_script (argv, SCRIPT_TIMEOUT,
+ NULL, 0, /* input buffer */
+ NULL, NULL, NULL); /* i/o callbacks */
+
+ g_free (project_path);
+
+ retval = FALSE;
+ if (data != NULL) {
+ if (data->error.length > 0 && err != NULL) {
+ /* the buffer is zero terminated */
+ *err = parse_errors (project, data->error.buffer);
+ }
+
+ if (data->output.length > 0) {
+ retval = parse_output_xml (project,
+ data->output.buffer,
+ data->output.length,
+ NULL);
+ } else {
+ /* FIXME: generate some kind of error here */
+ g_warning ("Child process returned no data");
+ }
+
+ spawn_data_destroy (data);
+ }
+
+ monitors_setup (project);
+
+ return retval;
+}
+
+static gboolean
+project_update (GbfMkfileProject *project,
+ xmlDocPtr doc,
+ GSList **change_set,
+ GError **err)
+{
+ GbfMkfileSpawnData *data;
+ gchar *argv [5];
+ gboolean retval;
+ gint i;
+ xmlChar *xml_doc;
+ int xml_size;
+
+ /* remove file monitors */
+ monitors_remove (project);
+
+ i = 0;
+ argv [i++] = GBF_MKFILE_PARSE;
+ DEBUG (argv [i++] = "-d");
+ argv [i++] = "--set";
+ argv [i++] = "-";
+ argv [i++] = NULL;
+ g_assert (i <= G_N_ELEMENTS (argv));
+
+ /* dump the document to memory */
+ xmlSubstituteEntitiesDefault (TRUE);
+ xmlDocDumpMemory (doc, &xml_doc, &xml_size);
+
+ /* execute the script */
+ data = spawn_script (argv, SCRIPT_TIMEOUT,
+ (gchar*)xml_doc, xml_size, /* input buffer */
+ NULL, NULL, NULL); /* i/o callbacks */
+ xmlFree (xml_doc);
+
+ retval = FALSE;
+ if (data != NULL) {
+ if (data->error.length > 0 && err != NULL) {
+ /* the buffer is zero terminated */
+ *err = parse_errors (project, data->error.buffer);
+ }
+
+ if (data->output.length > 0) {
+ /* process the xml output for changed groups */
+ retval = parse_output_xml (project,
+ data->output.buffer,
+ data->output.length,
+ change_set);
+ /* FIXME: emit this only if the project has indeed changed */
+ g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+ }
+
+ spawn_data_destroy (data);
+ }
+
+ monitors_setup (project);
+
+ return retval;
+}
+
+/*
+ * ---------------- Data structures managment
+ */
+
+static void
+gbf_mkfile_node_free (GbfMkfileNode *node)
+{
+ if (node) {
+ g_free (node->id);
+ g_free (node->name);
+ g_free (node->detail);
+ g_free (node->uri);
+ gbf_mkfile_config_mapping_destroy (node->config);
+
+ g_free (node);
+ }
+}
+
+static GNode *
+project_node_new (GbfMkfileNodeType type)
+{
+ GbfMkfileNode *node;
+
+ node = g_new0 (GbfMkfileNode, 1);
+ node->type = type;
+
+ return g_node_new (node);
+}
+
+static gboolean
+foreach_node_destroy (GNode *g_node,
+ gpointer data)
+{
+ GbfMkfileProject *project = data;
+
+ switch (GBF_MKFILE_NODE (g_node)->type) {
+ case GBF_MKFILE_NODE_GROUP:
+ g_hash_table_remove (project->groups, GBF_MKFILE_NODE (g_node)->id);
+ break;
+ case GBF_MKFILE_NODE_TARGET:
+ g_hash_table_remove (project->targets, GBF_MKFILE_NODE (g_node)->id);
+ break;
+ case GBF_MKFILE_NODE_SOURCE:
+ g_hash_table_remove (project->sources, GBF_MKFILE_NODE (g_node)->id);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ gbf_mkfile_node_free (GBF_MKFILE_NODE (g_node));
+
+ return FALSE;
+}
+
+static void
+project_node_destroy (GbfMkfileProject *project, GNode *g_node)
+{
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+
+ if (g_node) {
+ /* free each node's data first */
+ g_node_traverse (g_node,
+ G_IN_ORDER, G_TRAVERSE_ALL, -1,
+ foreach_node_destroy, project);
+
+ /* now destroy the tree itself */
+ g_node_destroy (g_node);
+ }
+}
+
+static void
+project_data_destroy (GbfMkfileProject *project)
+{
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+
+ monitors_remove (project);
+
+ /* project data */
+ project_node_destroy (project, project->root_node);
+ project->root_node = NULL;
+ g_free (project->project_file);
+ project->project_file = NULL;
+ gbf_mkfile_config_mapping_destroy (project->project_config);
+ project->project_config = NULL;
+
+ /* shortcut hash tables */
+ if (project->groups) g_hash_table_destroy (project->groups);
+ if (project->targets) g_hash_table_destroy (project->targets);
+ if (project->sources) g_hash_table_destroy (project->sources);
+ project->groups = NULL;
+ project->targets = NULL;
+ project->sources = NULL;
+}
+
+static void
+project_data_init (GbfMkfileProject *project)
+{
+ g_return_if_fail (project != NULL);
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+
+ /* free data if necessary */
+ project_data_destroy (project);
+
+ /* FIXME: initialize monitors here, since monitors' lifecycle
+ * are bound to source files from the project */
+
+ /* project data */
+ project->project_file = NULL;
+ project->project_config = gbf_mkfile_config_mapping_new ();
+ project->root_node = NULL;
+
+ /* shortcut hash tables */
+ project->groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ project->targets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ project->sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+}
+
+GbfMkfileConfigMapping *
+gbf_mkfile_project_get_config (GbfMkfileProject *project, GError **error)
+{
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return gbf_mkfile_config_mapping_copy (project->project_config);
+}
+
+GbfMkfileConfigMapping *
+gbf_mkfile_project_get_group_config (GbfMkfileProject *project, const gchar *group_id,
+ GError **error)
+{
+ GbfMkfileNode *node;
+ GNode *g_node;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_node = g_hash_table_lookup (project->groups, group_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group doesn't exist"));
+ return NULL;
+ }
+ node = GBF_MKFILE_NODE (g_node);
+ return gbf_mkfile_config_mapping_copy (node->config);
+}
+
+GbfMkfileConfigMapping *
+gbf_mkfile_project_get_target_config (GbfMkfileProject *project, const gchar *target_id,
+ GError **error)
+{
+ GbfMkfileNode *node;
+ GNode *g_node;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_node = g_hash_table_lookup (project->targets, target_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target doesn't exist"));
+ return NULL;
+ }
+ node = GBF_MKFILE_NODE (g_node);
+ return gbf_mkfile_config_mapping_copy (node->config);
+}
+
+void
+gbf_mkfile_project_set_config (GbfMkfileProject *project,
+ GbfMkfileConfigMapping *new_config, GError **error)
+{
+ xmlDocPtr doc;
+ GSList *change_set = NULL;
+
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+ g_return_if_fail (new_config != NULL);
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+
+ if (!xml_write_set_config (project, doc, NULL, new_config)) {
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/set-config.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return;
+ }
+ xmlFreeDoc (doc);
+ change_set_destroy (change_set);
+}
+
+void
+gbf_mkfile_project_set_group_config (GbfMkfileProject *project, const gchar *group_id,
+ GbfMkfileConfigMapping *new_config, GError **error)
+{
+ GbfMkfileNode *node;
+ xmlDocPtr doc;
+ GNode *g_node;
+ GSList *change_set = NULL;
+
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+ g_return_if_fail (new_config != NULL);
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ g_node = g_hash_table_lookup (project->groups, group_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group doesn't exist"));
+ return;
+ }
+ node = GBF_MKFILE_NODE (g_node);
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_set_config (project, doc, g_node, new_config)) {
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/set-config.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return;
+ }
+ xmlFreeDoc (doc);
+ change_set_destroy (change_set);
+}
+
+void
+gbf_mkfile_project_set_target_config (GbfMkfileProject *project,
+ const gchar *target_id,
+ GbfMkfileConfigMapping *new_config,
+ GError **error)
+{
+ xmlDocPtr doc;
+ GNode *g_node;
+ GSList *change_set = NULL;
+
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (project));
+ g_return_if_fail (new_config != NULL);
+ g_return_if_fail (error == NULL || *error == NULL);
+
+ g_node = g_hash_table_lookup (project->targets, target_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target doesn't exist"));
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_set_config (project, doc, g_node, new_config)) {
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/set-config.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return;
+ }
+ xmlFreeDoc (doc);
+ change_set_destroy (change_set);
+}
+
+/*
+ * GbfProjectIface methods ------------------------------------------
+ */
+
+static void
+impl_load (GbfProject *_project,
+ const gchar *uri,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ gchar *root_path;
+
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (_project));
+
+ project = GBF_MKFILE_PROJECT (_project);
+ if (project->project_root_uri) {
+ /* FIXME:
+ * - do we really want to allow object reutilization
+ * - cancel some pending operations in the queue?
+ */
+ project_data_destroy (project);
+ g_free (project->project_root_uri);
+ project->project_root_uri = NULL;
+
+ project_data_init (project);
+ }
+
+ /* allow this? */
+ if (uri == NULL)
+ return;
+
+ /* check that the uri is in the filesystem */
+ project->project_root_uri = uri_normalize (uri, NULL);
+ if (project->project_root_uri == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Invalid or remote path (only local paths supported)"));
+ return;
+ }
+
+ /* some basic checks */
+ root_path = uri_to_path (project->project_root_uri);
+ if (root_path == NULL || !g_file_test (root_path, G_FILE_TEST_IS_DIR)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Project doesn't exist or invalid path"));
+ g_free (root_path);
+ g_free (project->project_root_uri);
+ project->project_root_uri = NULL;
+ return;
+ }
+ g_free (root_path);
+
+ /* now try loading the project */
+ if (!project_reload (project, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Malformed project"));
+ g_free (project->project_root_uri);
+ project->project_root_uri = NULL;
+ }
+}
+
+static gboolean
+file_exists (const gchar *path, const gchar *filename)
+{
+ gchar *full_path;
+ gboolean retval;
+
+ full_path = g_build_filename (path, filename, NULL);
+ retval = g_file_test (full_path, G_FILE_TEST_EXISTS);
+ g_free (full_path);
+
+ return retval;
+}
+
+static gboolean
+impl_probe (GbfProject *_project,
+ const gchar *path,
+ GError **error)
+{
+ gchar *normalized_uri, *root_path;
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), FALSE);
+
+ normalized_uri = uri_normalize (path, NULL);
+ if (normalized_uri != NULL) {
+ root_path = uri_to_path (normalized_uri);
+ if (root_path != NULL && g_file_test (root_path, G_FILE_TEST_IS_DIR)) {
+ retval = ((file_exists (root_path, "Makefile") ||
+ file_exists (root_path, "makefile")) &&
+ !(file_exists (root_path, "Makefile.am") ||
+ file_exists (root_path, "Makefile.in")));
+ g_free (root_path);
+ }
+ g_free (normalized_uri);
+ }
+
+ return retval;
+}
+
+static void
+impl_refresh (GbfProject *_project,
+ GError **error)
+{
+ GbfMkfileProject *project;
+
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (_project));
+
+ project = GBF_MKFILE_PROJECT (_project);
+
+ if (project_reload (project, error))
+ g_signal_emit_by_name (G_OBJECT (project), "project-updated");
+}
+
+static GbfProjectCapabilities
+impl_get_capabilities (GbfProject *_project, GError **error)
+{
+ return GBF_PROJECT_CAN_ADD_NONE;
+}
+
+static GbfProjectGroup *
+impl_get_group (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GbfProjectGroup *group;
+ GNode *g_node;
+ GbfMkfileNode *node;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+ project = GBF_MKFILE_PROJECT (_project);
+ g_node = g_hash_table_lookup (project->groups, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group doesn't exist"));
+ return NULL;
+ }
+ node = GBF_MKFILE_NODE (g_node);
+
+ group = g_new0 (GbfProjectGroup, 1);
+ group->id = g_strdup (node->id);
+ group->name = g_strdup (node->name);
+ if (g_node->parent)
+ group->parent_id = g_strdup (GBF_MKFILE_NODE (g_node->parent)->id);
+ else
+ group->parent_id = NULL;
+ group->groups = NULL;
+ group->targets = NULL;
+
+ /* add subgroups and targets of the group */
+ g_node = g_node_first_child (g_node);
+ while (g_node) {
+ node = GBF_MKFILE_NODE (g_node);
+ switch (node->type) {
+ case GBF_MKFILE_NODE_GROUP:
+ group->groups = g_list_prepend (group->groups,
+ g_strdup (node->id));
+ break;
+ case GBF_MKFILE_NODE_TARGET:
+ group->targets = g_list_prepend (group->targets,
+ g_strdup (node->id));
+ break;
+ default:
+ break;
+ }
+ g_node = g_node_next_sibling (g_node);
+ }
+ group->groups = g_list_reverse (group->groups);
+ group->targets = g_list_reverse (group->targets);
+
+ return group;
+}
+
+static void
+foreach_group (gpointer key, gpointer value, gpointer data)
+{
+ GList **groups = data;
+
+ *groups = g_list_prepend (*groups, g_strdup (key));
+}
+
+
+static GList *
+impl_get_all_groups (GbfProject *_project,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GList *groups = NULL;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+ project = GBF_MKFILE_PROJECT (_project);
+ g_hash_table_foreach (project->groups, foreach_group, &groups);
+
+ return groups;
+}
+
+static GtkWidget *
+impl_configure_new_group (GbfProject *_project,
+ GError **error)
+{
+ UNIMPLEMENTED;
+
+ return NULL;
+}
+
+static GtkWidget *
+impl_configure_group (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GtkWidget *wid = NULL;
+ GError *err = NULL;
+
+ g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ wid = gbf_mkfile_properties_get_group_widget (GBF_MKFILE_PROJECT (_project),
+ id, &err);
+ if (err) {
+ g_propagate_error (error, err);
+ }
+ return wid;
+}
+
+static gchar *
+impl_add_group (GbfProject *_project,
+ const gchar *parent_id,
+ const gchar *name,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GNode *g_node, *iter_node;
+ xmlDocPtr doc;
+ GSList *change_set = NULL;
+ GbfMkfileChange *change;
+ gchar *retval;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+ project = GBF_MKFILE_PROJECT (_project);
+
+ /* find the parent group */
+ g_node = g_hash_table_lookup (project->groups, parent_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Parent group doesn't exist"));
+ return NULL;
+ }
+
+ /* check that the new group doesn't already exist */
+ iter_node = g_node_first_child (g_node);
+ while (iter_node) {
+ GbfMkfileNode *node = GBF_MKFILE_NODE (iter_node);
+ if (node->type == GBF_MKFILE_NODE_GROUP &&
+ !strcmp (node->name, name)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group already exists"));
+ return NULL;
+ }
+ iter_node = g_node_next_sibling (iter_node);
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_add_group (project, doc, g_node, name)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group couldn't be created"));
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/add-group.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+ xmlFreeDoc (doc);
+
+ /* get newly created group id */
+ retval = NULL;
+ DEBUG (change_set_debug_print (change_set));
+ change = change_set_find (change_set, GBF_MKFILE_CHANGE_ADDED, GBF_MKFILE_NODE_GROUP);
+ if (change) {
+ retval = g_strdup (change->id);
+ } else {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group couldn't be created"));
+ }
+ change_set_destroy (change_set);
+
+ return retval;
+}
+
+static void
+impl_remove_group (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GNode *g_node;
+ xmlDocPtr doc;
+ GSList *change_set = NULL;
+
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (_project));
+
+ project = GBF_MKFILE_PROJECT (_project);
+
+ /* Find the target */
+ g_node = g_hash_table_lookup (project->groups, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group doesn't exist"));
+ return;
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_remove_group (project, doc, g_node)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group coudn't be removed"));
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/remove-group.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ }
+ xmlFreeDoc (doc);
+ change_set_destroy (change_set);
+}
+
+static GbfProjectTarget *
+impl_get_target (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GbfProjectTarget *target;
+ GNode *g_node;
+ GbfMkfileNode *node;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+ project = GBF_MKFILE_PROJECT (_project);
+ g_node = g_hash_table_lookup (project->targets, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target doesn't exist"));
+ return NULL;
+ }
+ node = GBF_MKFILE_NODE (g_node);
+
+ target = g_new0 (GbfProjectTarget, 1);
+ target->id = g_strdup (node->id);
+ target->name = g_strdup (node->name);
+ target->type = g_strdup (node->detail);
+ target->group_id = g_strdup (GBF_MKFILE_NODE (g_node->parent)->id);
+ target->sources = NULL;
+
+ /* add sources to the target */
+ g_node = g_node_first_child (g_node);
+ while (g_node) {
+ node = GBF_MKFILE_NODE (g_node);
+ switch (node->type) {
+ case GBF_MKFILE_NODE_SOURCE:
+ target->sources = g_list_prepend (target->sources,
+ g_strdup (node->id));
+ break;
+ default:
+ break;
+ }
+ g_node = g_node_next_sibling (g_node);
+ }
+ target->sources = g_list_reverse (target->sources);
+
+ return target;
+}
+
+static void
+foreach_target (gpointer key, gpointer value, gpointer data)
+{
+ GList **targets = data;
+
+ *targets = g_list_prepend (*targets, g_strdup (key));
+}
+
+static GList *
+impl_get_all_targets (GbfProject *_project,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GList *targets = NULL;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+ project = GBF_MKFILE_PROJECT (_project);
+ g_hash_table_foreach (project->targets, foreach_target, &targets);
+
+ return targets;
+}
+
+static GtkWidget *
+impl_configure_new_target (GbfProject *_project,
+ GError **error)
+{
+ UNIMPLEMENTED;
+
+ return NULL;
+}
+
+static GtkWidget *
+impl_configure_target (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GtkWidget *wid = NULL;
+ GError *err = NULL;
+
+ g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ wid = gbf_mkfile_properties_get_target_widget (GBF_MKFILE_PROJECT (_project),
+ id, &err);
+ if (err) {
+ g_propagate_error (error, err);
+ }
+ return wid;
+}
+
+static char *
+impl_add_target (GbfProject *_project,
+ const gchar *group_id,
+ const gchar *name,
+ const gchar *type,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GNode *g_node, *iter_node;
+ xmlDocPtr doc;
+ GSList *change_set = NULL;
+ GbfMkfileChange *change;
+ gchar *retval;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+ project = GBF_MKFILE_PROJECT (_project);
+
+ /* find the group */
+ g_node = g_hash_table_lookup (project->groups, group_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Group doesn't exist"));
+ return NULL;
+ }
+
+ /* FIXME: maybe pre-check the target type? */
+
+ /* check that the target doesn't already exist */
+ iter_node = g_node_first_child (g_node);
+ while (iter_node) {
+ GbfMkfileNode *node = GBF_MKFILE_NODE (iter_node);
+ if (node->type == GBF_MKFILE_NODE_TARGET &&
+ !strcmp (node->name, name)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target already exists"));
+ return NULL;
+ }
+ iter_node = g_node_next_sibling (iter_node);
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_add_target (project, doc, g_node, name, type)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target couldn't be created"));
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/add-target.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+ xmlFreeDoc (doc);
+
+ /* get newly created target id */
+ retval = NULL;
+ DEBUG (change_set_debug_print (change_set));
+ change = change_set_find (change_set, GBF_MKFILE_CHANGE_ADDED, GBF_MKFILE_NODE_TARGET);
+ if (change) {
+ retval = g_strdup (change->id);
+ } else {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target couldn't be created"));
+ }
+ change_set_destroy (change_set);
+
+ return retval;
+}
+
+static void
+impl_remove_target (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GNode *g_node;
+ xmlDocPtr doc;
+ GSList *change_set = NULL;
+
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (_project));
+
+ project = GBF_MKFILE_PROJECT (_project);
+
+ /* Find the target */
+ g_node = g_hash_table_lookup (project->targets, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target doesn't exist"));
+ return;
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_remove_target (project, doc, g_node)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target coudn't be removed"));
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/remove-target.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ }
+ xmlFreeDoc (doc);
+ change_set_destroy (change_set);
+}
+
+static const gchar *
+impl_name_for_type (GbfProject *_project,
+ const gchar *type)
+{
+ if (!strcmp (type, "static_lib")) {
+ return _("Static Library");
+ } else if (!strcmp (type, "shared_lib")) {
+ return _("Shared Library");
+ } else if (!strcmp (type, "man")) {
+ return _("Man Documentation");
+ } else if (!strcmp (type, "data")) {
+ return _("Miscellaneous Data");
+ } else if (!strcmp (type, "program")) {
+ return _("Program");
+ } else if (!strcmp (type, "script")) {
+ return _("Script");
+ } else if (!strcmp (type, "info")) {
+ return _("Info Documentation");
+ } else {
+ return _("Unknown");
+ }
+}
+
+static const gchar *
+impl_mimetype_for_type (GbfProject *_project,
+ const gchar *type)
+{
+ if (!strcmp (type, "static_lib")) {
+ return "application/x-archive";
+ } else if (!strcmp (type, "shared_lib")) {
+ return "application/x-sharedlib";
+ } else if (!strcmp (type, "man")) {
+ return "text/x-troff-man";
+ } else if (!strcmp (type, "data")) {
+ return "application/octet-stream";
+ } else if (!strcmp (type, "program")) {
+ return "application/x-executable";
+ } else if (!strcmp (type, "script")) {
+ return "text/x-shellscript";
+ } else if (!strcmp (type, "info")) {
+ return "application/x-tex-info";
+ } else {
+ return "text/plain";
+ }
+}
+
+static gchar **
+impl_get_types (GbfProject *_project)
+{
+ return g_strsplit ("program:shared_lib:static_lib:"
+ "man:data:script:info", ":", 0);
+}
+
+static GbfProjectTargetSource *
+impl_get_source (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GbfProjectTargetSource *source;
+ GNode *g_node;
+ GbfMkfileNode *node;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+ project = GBF_MKFILE_PROJECT (_project);
+ g_node = g_hash_table_lookup (project->sources, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Source doesn't exist"));
+ return NULL;
+ }
+ node = GBF_MKFILE_NODE (g_node);
+
+ source = g_new0 (GbfProjectTargetSource, 1);
+ source->id = g_strdup (node->id);
+ source->source_uri = g_strdup (node->uri);
+ source->target_id = g_strdup (GBF_MKFILE_NODE (g_node->parent)->id);
+
+ return source;
+}
+
+static void
+foreach_source (gpointer key, gpointer value, gpointer data)
+{
+ GList **sources = data;
+
+ *sources = g_list_prepend (*sources, g_strdup (key));
+}
+
+static GList *
+impl_get_all_sources (GbfProject *_project,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GList *sources = NULL;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+ project = GBF_MKFILE_PROJECT (_project);
+ g_hash_table_foreach (project->sources, foreach_source, &sources);
+
+ return sources;
+}
+
+static GtkWidget *
+impl_configure_new_source (GbfProject *_project,
+ GError **error)
+{
+ UNIMPLEMENTED;
+
+ return NULL;
+}
+
+static GtkWidget *
+impl_configure_source (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ UNIMPLEMENTED;
+
+ return NULL;
+}
+
+/**
+ * impl_add_source:
+ * @project:
+ * @target_id: the target ID to where to add the source
+ * @uri: an uri to the file, which can be absolute or relative to the target's group
+ * @error:
+ *
+ * Add source implementation. The uri must have the project root as its parent.
+ *
+ * Return value:
+ **/
+static gchar *
+impl_add_source (GbfProject *_project,
+ const gchar *target_id,
+ const gchar *uri,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GNode *g_node, *iter_node;
+ xmlDocPtr doc;
+ gboolean abort_action = FALSE;
+ gchar *full_uri = NULL;
+ gchar *group_uri;
+ GSList *change_set = NULL;
+ GbfMkfileChange *change;
+ gchar *retval;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (_project), NULL);
+
+ project = GBF_MKFILE_PROJECT (_project);
+
+ /* check target */
+ g_node = g_hash_table_lookup (project->targets, target_id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Target doesn't exist"));
+ return NULL;
+ }
+
+ /* if the uri is relative, resolve it against the target's
+ * group directory; we need to compute the group's uri
+ * first */
+ group_uri = uri_normalize (g_path_skip_root (GBF_MKFILE_NODE (g_node->parent)->id),
+ project->project_root_uri);
+ full_uri = uri_normalize (uri, group_uri);
+ g_free (group_uri);
+
+ /* Check that the source uri is inside the project root */
+ if (!uri_is_parent (project->project_root_uri, full_uri)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Source file must be inside the project directory"));
+ abort_action = TRUE;
+ }
+
+ /* check for source duplicates */
+ iter_node = g_node_first_child (g_node);
+ while (!abort_action && iter_node) {
+ GbfMkfileNode *node = GBF_MKFILE_NODE (iter_node);
+
+ if (node->type == GBF_MKFILE_NODE_SOURCE &&
+ uri_is_equal (full_uri, node->uri)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Source is already in target"));
+ abort_action = TRUE;
+ }
+ iter_node = g_node_next_sibling (iter_node);
+ }
+
+ /* have there been any errors? */
+ if (abort_action) {
+ g_free (full_uri);
+ return NULL;
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+
+ if (!xml_write_add_source (project, doc, g_node, full_uri)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Source couldn't be added"));
+ abort_action = TRUE;
+ }
+
+ g_free (full_uri);
+ if (abort_action) {
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/add-source.xml", doc);
+ });
+
+ /* Update the project */
+ if (!project_update (project, doc, &change_set, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+ xmlFreeDoc (doc);
+
+ /* get newly created source id */
+ retval = NULL;
+ DEBUG (change_set_debug_print (change_set));
+ change = change_set_find (change_set, GBF_MKFILE_CHANGE_ADDED, GBF_MKFILE_NODE_SOURCE);
+ if (change) {
+ retval = g_strdup (change->id);
+ } else {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Source couldn't be added"));
+ }
+ change_set_destroy (change_set);
+
+ return retval;
+}
+
+static void
+impl_remove_source (GbfProject *_project,
+ const gchar *id,
+ GError **error)
+{
+ GbfMkfileProject *project;
+ GNode *g_node;
+ xmlDocPtr doc;
+
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (_project));
+
+ project = GBF_MKFILE_PROJECT (_project);
+
+ /* Find the source */
+ g_node = g_hash_table_lookup (project->sources, id);
+ if (g_node == NULL) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Source doesn't exist"));
+ return;
+ }
+
+ /* Create the update xml */
+ doc = xml_new_change_doc (project);
+ if (!xml_write_remove_source (project, doc, g_node)) {
+ error_set (error, GBF_PROJECT_ERROR_DOESNT_EXIST,
+ _("Source coudn't be removed"));
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ DEBUG ({
+ xmlSetDocCompressMode (doc, 0);
+ xmlSaveFile ("/tmp/remove-source.xml", doc);
+ });
+
+ /* Update the project */
+ /* FIXME: should get and process the change set to verify that
+ * the source has been removed? */
+ if (!project_update (project, doc, NULL, error)) {
+ error_set (error, GBF_PROJECT_ERROR_PROJECT_MALFORMED,
+ _("Unable to update project"));
+ }
+ xmlFreeDoc (doc);
+}
+
+static GtkWidget *
+impl_configure (GbfProject *_project, GError **error)
+{
+ GtkWidget *wid = NULL;
+ GError *err = NULL;
+
+ g_return_val_if_fail (GBF_IS_PROJECT (_project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ wid = gbf_mkfile_properties_get_widget (GBF_MKFILE_PROJECT (_project), &err);
+ if (err) {
+ g_propagate_error (error, err);
+ }
+ return wid;
+}
+
+static GList *
+impl_get_config_modules (GbfProject *project, GError **error)
+{
+ return NULL;
+}
+
+static GList *
+impl_get_config_packages (GbfProject *project,
+ const gchar* module,
+ GError **error)
+{
+ return NULL;
+}
+
+static void
+gbf_mkfile_project_class_init (GbfMkfileProjectClass *klass)
+{
+ GObjectClass *object_class;
+ GbfProjectClass *project_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ project_class = GBF_PROJECT_CLASS (klass);
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->dispose = gbf_mkfile_project_dispose;
+ object_class->get_property = gbf_mkfile_project_get_property;
+
+ project_class->load = impl_load;
+ project_class->probe = impl_probe;
+ project_class->refresh = impl_refresh;
+ project_class->get_capabilities = impl_get_capabilities;
+
+ project_class->add_group = impl_add_group;
+ project_class->remove_group = impl_remove_group;
+ project_class->get_group = impl_get_group;
+ project_class->get_all_groups = impl_get_all_groups;
+ project_class->configure_group = impl_configure_group;
+ project_class->configure_new_group = impl_configure_new_group;
+
+ project_class->add_target = impl_add_target;
+ project_class->remove_target = impl_remove_target;
+ project_class->get_target = impl_get_target;
+ project_class->get_all_targets = impl_get_all_targets;
+ project_class->configure_target = impl_configure_target;
+ project_class->configure_new_target = impl_configure_new_target;
+
+ project_class->add_source = impl_add_source;
+ project_class->remove_source = impl_remove_source;
+ project_class->get_source = impl_get_source;
+ project_class->get_all_sources = impl_get_all_sources;
+ project_class->configure_source = impl_configure_source;
+ project_class->configure_new_source = impl_configure_new_source;
+
+ project_class->configure = impl_configure;
+ project_class->name_for_type = impl_name_for_type;
+ project_class->mimetype_for_type = impl_mimetype_for_type;
+ project_class->get_types = impl_get_types;
+
+ project_class->get_config_modules = impl_get_config_modules;
+ project_class->get_config_packages = impl_get_config_packages;
+
+ /* default signal handlers */
+ project_class->project_updated = NULL;
+
+ /* FIXME: shouldn't we use '_' instead of '-' ? */
+ g_object_class_install_property
+ (object_class, PROP_PROJECT_DIR,
+ g_param_spec_string ("project-dir",
+ _("Project directory"),
+ _("Project directory"),
+ NULL,
+ G_PARAM_READABLE));
+}
+
+static void
+gbf_mkfile_project_instance_init (GbfMkfileProject *project)
+{
+ /* initialize data & monitors */
+ project->project_root_uri = NULL;
+ project_data_init (project);
+
+ /* setup queue */
+ project->queue_ops = g_queue_new ();
+ project->queue_handler_tag = 0;
+
+ /* initialize build callbacks */
+ project->callbacks = NULL;
+
+ /* FIXME: those path should be configurable */
+ project->make_command = g_strdup ("/usr/bin/make");
+ project->configure_command = g_strdup ("./configure");
+ project->autogen_command = g_strdup ("./autogen.sh");
+ project->install_prefix = g_strdup ("/gnome");
+}
+
+static void
+gbf_mkfile_project_dispose (GObject *object)
+{
+ GbfMkfileProject *project;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GBF_IS_MKFILE_PROJECT (object));
+
+ project = GBF_MKFILE_PROJECT (object);
+
+ /* project data & monitors */
+ project_data_destroy (project);
+ g_free (project->project_root_uri);
+ project->project_root_uri = NULL;
+
+ g_free (project->make_command);
+ g_free (project->configure_command);
+ g_free (project->autogen_command);
+ g_free (project->install_prefix);
+
+ GNOME_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
+}
+
+static void
+gbf_mkfile_project_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbfMkfileProject *project = GBF_MKFILE_PROJECT (object);
+
+ switch (prop_id) {
+ case PROP_PROJECT_DIR:
+ g_value_set_string (value, project->project_root_uri);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+GbfProject *
+gbf_mkfile_project_new (void)
+{
+ return GBF_PROJECT (g_object_new (GBF_TYPE_MKFILE_PROJECT, NULL));
+}
+
+GBF_BACKEND_BOILERPLATE (GbfMkfileProject, gbf_mkfile_project);
Added: trunk/plugins/gbf-mkfile/gbf-mkfile-project.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-project.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,135 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-mkfile-project.h
+ *
+ * Copyright (C) 2005 Eric Greveson
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ * JP Rosevear
+ * Dave Camp
+ * Naba Kumar
+ * Gustavo GirÃldez
+ */
+
+#ifndef _GBF_MKFILE_PROJECT_H_
+#define _GBF_MKFILE_PROJECT_H_
+
+#include <glib-object.h>
+#include <libanjuta/gbf-project.h>
+#include "gbf-mkfile-config.h"
+
+G_BEGIN_DECLS
+
+#define GBF_TYPE_MKFILE_PROJECT (gbf_mkfile_project_get_type (NULL))
+#define GBF_MKFILE_PROJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GBF_TYPE_MKFILE_PROJECT, GbfMkfileProject))
+#define GBF_MKFILE_PROJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GBF_TYPE_MKFILE_PROJECT, GbfMkfileProjectClass))
+#define GBF_IS_MKFILE_PROJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GBF_TYPE_MKFILE_PROJECT))
+#define GBF_IS_MKFILE_PROJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GBF_TYPE_MKFILE_PROJECT))
+
+typedef struct _GbfMkfileProject GbfMkfileProject;
+typedef struct _GbfMkfileProjectClass GbfMkfileProjectClass;
+typedef struct _GbfMkfileBuildCallback GbfMkfileBuildCallback;
+typedef struct _GbfMkfileNode GbfMkfileNode;
+
+typedef enum {
+ GBF_MKFILE_NODE_GROUP,
+ GBF_MKFILE_NODE_TARGET,
+ GBF_MKFILE_NODE_SOURCE
+} GbfMkfileNodeType;
+
+struct _GbfMkfileNode {
+ GbfMkfileNodeType type;
+ gchar *id; /* unique id among nodes of the same type */
+ gchar *name; /* user visible string */
+ GbfMkfileConfigMapping *config;
+ gchar *uri; /* groups: path to Makefile.am;
+ targets: NULL;
+ sources: file uri */
+ gchar *detail; /* groups: NULL;
+ targets: target type;
+ sources: NULL or target dependency for built sources */
+};
+
+struct _GbfMkfileProject {
+ GbfProject parent;
+
+ /* uri of the project; this can be a full uri, even though we
+ * can only work with native local files */
+ gchar *project_root_uri;
+
+ /* project data */
+ gchar *project_file; /* configure.in uri */
+ GbfMkfileConfigMapping *project_config; /* project configuration
+ * (i.e. from configure.in) */
+ GNode *root_node; /* tree containing project data;
+ * each GNode's data is a
+ * GbfAmNode, and the root of
+ * the tree is the root group. */
+
+ /* shortcut hash tables, mapping id -> GNode from the tree above */
+ GHashTable *groups;
+ GHashTable *targets;
+ GHashTable *sources;
+
+ /* project files monitors */
+ GHashTable *monitors;
+
+ /* operations queue */
+ GQueue *queue_ops;
+ guint queue_handler_tag;
+
+ /* build callbacks */
+ GList *callbacks;
+
+ /* build config */
+ gchar *make_command;
+ gchar *configure_command;
+ gchar *autogen_command;
+ gchar *install_prefix;
+};
+
+struct _GbfMkfileProjectClass {
+ GbfProjectClass parent_class;
+};
+
+/* convenient shortcut macro the get the GbfMkfileNode from a GNode */
+#define GBF_MKFILE_NODE(g_node) ((g_node) != NULL ? (GbfMkfileNode *)((g_node)->data) : NULL)
+
+GType gbf_mkfile_project_get_type (GTypeModule *module);
+GbfProject *gbf_mkfile_project_new (void);
+
+/* FIXME: The config infrastructure should probably be made part of GbfProject
+ * so that other backend implementations could use them directly and we don't
+ * have to create separate configuration widgets. But then different back end
+ * implementations could have significantly different config management
+ * warranting separate implementations.
+ */
+/* These functions returns a copy of the config. It should be free with
+ * gbf_mkfile_config_value_free() when no longer required
+ */
+GbfMkfileConfigMapping *gbf_mkfile_project_get_config (GbfMkfileProject *project, GError **error);
+GbfMkfileConfigMapping *gbf_mkfile_project_get_group_config (GbfMkfileProject *project, const gchar *group_id, GError **error);
+GbfMkfileConfigMapping *gbf_mkfile_project_get_target_config (GbfMkfileProject *project, const gchar *target_id, GError **error);
+
+void gbf_mkfile_project_set_config (GbfMkfileProject *project, GbfMkfileConfigMapping *new_config, GError **error);
+void gbf_mkfile_project_set_group_config (GbfMkfileProject *project, const gchar *group_id, GbfMkfileConfigMapping *new_config, GError **error);
+void gbf_mkfile_project_set_target_config (GbfMkfileProject *project, const gchar *target_id, GbfMkfileConfigMapping *new_config, GError **error);
+
+G_END_DECLS
+
+#endif /* _GBF_MKFILE_PROJECT_H_ */
Added: trunk/plugins/gbf-mkfile/gbf-mkfile-properties.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-properties.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,428 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-mkfile-properties.c
+ *
+ * Copyright (C) 2005 Eric Greveson
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ * JP Rosevear
+ * Dave Camp
+ * Naba Kumar
+ * Gustavo GirÃldez
+ */
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "gbf-mkfile-config.h"
+#include "gbf-mkfile-properties.h"
+
+typedef enum {
+ GBF_MKFILE_CONFIG_LABEL,
+ GBF_MKFILE_CONFIG_ENTRY,
+ GBF_MKFILE_CONFIG_TEXT,
+ GBF_MKFILE_CONFIG_LIST,
+} GbfConfigPropertyType;
+
+static void
+on_property_entry_changed (GtkEntry *entry, GbfMkfileConfigValue *value)
+{
+ gbf_mkfile_config_value_set_string (value, gtk_entry_get_text (entry));
+}
+
+static void
+add_configure_property (GbfMkfileProject *project, GbfMkfileConfigMapping *config,
+ GbfConfigPropertyType prop_type,
+ const gchar *display_name, const gchar *direct_value,
+ const gchar *config_key, GtkWidget *table,
+ gint position)
+{
+ GtkWidget *label;
+ const gchar *value;
+ GtkWidget *widget;
+ GbfMkfileConfigValue *config_value = NULL;
+
+ value = "";
+ if (direct_value)
+ {
+ value = direct_value;
+ } else {
+ config_value = gbf_mkfile_config_mapping_lookup (config,
+ config_key);
+ if (!config_value) {
+ config_value = gbf_mkfile_config_value_new (GBF_MKFILE_TYPE_STRING);
+ gbf_mkfile_config_value_set_string (config_value, "");
+ gbf_mkfile_config_mapping_insert (config, config_key,
+ config_value);
+ }
+ if (config_value && config_value->type == GBF_MKFILE_TYPE_STRING) {
+ const gchar *val_str;
+ val_str = gbf_mkfile_config_value_get_string (config_value);
+ if (val_str)
+ value = val_str;
+ }
+ }
+
+ label = gtk_label_new (display_name);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, -1);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, position, position+1,
+ GTK_FILL, GTK_FILL, 5, 3);
+ switch (prop_type) {
+ case GBF_MKFILE_CONFIG_LABEL:
+ widget = gtk_label_new (value);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+ break;
+ case GBF_MKFILE_CONFIG_ENTRY:
+ widget = gtk_entry_new ();
+ gtk_entry_set_text (GTK_ENTRY (widget), value);
+ if (config_value) {
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (on_property_entry_changed),
+ config_value);
+ }
+ break;
+ default:
+ g_warning ("Should not reach here");
+ widget = gtk_label_new (_("Unknown"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+ }
+ gtk_widget_show (widget);
+ gtk_table_attach (GTK_TABLE (table), widget, 1, 2, position, position+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+}
+
+static void
+recursive_config_foreach_cb (const gchar *key, GbfMkfileConfigValue *value,
+ gpointer user_data)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+ GtkWidget *widget;
+ gint position;
+
+ table = GTK_WIDGET (user_data);
+ position = g_list_length (GTK_TABLE (table)->children);
+
+ label = gtk_label_new (key);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, -1);
+ gtk_widget_show (label);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1,
+ position, position+1,
+ GTK_FILL, GTK_FILL, 5, 3);
+
+ if (value->type == GBF_MKFILE_TYPE_STRING) {
+ widget = gtk_entry_new ();
+ gtk_entry_set_text (GTK_ENTRY (widget),
+ gbf_mkfile_config_value_get_string (value));
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (on_property_entry_changed),
+ value);
+ } else if (value->type == GBF_MKFILE_TYPE_LIST) {
+ /* FIXME: */
+ widget = gtk_label_new ("FIXME");
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+ } else if (value->type == GBF_MKFILE_TYPE_MAPPING) {
+ /* FIXME: */
+ widget = gtk_label_new ("FIXME");
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+ } else {
+ g_warning ("Should not be here");
+ widget = gtk_label_new (_("Unknown"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, -1);
+ }
+ gtk_widget_show (widget);
+ gtk_table_attach (GTK_TABLE (table), widget, 1, 2,
+ position, position+1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+}
+
+GtkWidget*
+gbf_mkfile_properties_get_widget (GbfMkfileProject *project, GError **error)
+{
+ GbfMkfileConfigMapping *config;
+ GtkWidget *table;
+ GError *err = NULL;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ config = gbf_mkfile_project_get_config (project, &err);
+ if (err)
+ {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+ table = gtk_table_new (7, 2, FALSE);
+
+ /* Group name */
+ add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+ _("Project:"), project->project_root_uri,
+ NULL, table, 0);
+
+ gtk_widget_show_all (table);
+ return table;
+}
+
+static void
+on_group_widget_destroy (GtkWidget *wid, GtkWidget *table)
+{
+ GError *err = NULL;
+
+ GbfMkfileProject *project = g_object_get_data (G_OBJECT (table), "__project");
+ GbfMkfileConfigMapping *new_config = g_object_get_data (G_OBJECT (table), "__config");
+ const gchar *group_id = g_object_get_data (G_OBJECT (table), "__group_id");
+ gbf_mkfile_project_set_group_config (project, group_id, new_config, &err);
+ if (err)
+ {
+ g_warning ("%s", err->message);
+ g_error_free (err);
+ }
+ g_object_unref (table);
+}
+
+GtkWidget*
+gbf_mkfile_properties_get_group_widget (GbfMkfileProject *project,
+ const gchar *group_id,
+ GError **error)
+{
+ GbfProjectGroup *group;
+ GbfMkfileConfigMapping *config;
+ GbfMkfileConfigValue *value;
+ GtkWidget *table;
+ GError *err = NULL;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ group = gbf_project_get_group (GBF_PROJECT (project), group_id, &err);
+ if (err)
+ {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+ config = gbf_mkfile_project_get_group_config (project, group_id, &err);
+ if (err)
+ {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+
+ g_return_val_if_fail (group != NULL, NULL);
+ g_return_val_if_fail (config != NULL, NULL);
+
+ table = gtk_table_new (7, 2, FALSE);
+ g_object_ref (table);
+ g_object_set_data (G_OBJECT (table), "__project", project);
+ g_object_set_data_full (G_OBJECT (table), "__config", config,
+ (GDestroyNotify)gbf_mkfile_config_mapping_destroy);
+ g_object_set_data_full (G_OBJECT (table), "__group_id",
+ g_strdup (group_id),
+ (GDestroyNotify)g_free);
+ g_signal_connect (table, "destroy",
+ G_CALLBACK (on_group_widget_destroy), table);
+
+ /* Group name */
+ add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+ _("Group name:"), group->name, NULL, table, 0);
+ /* Includes */
+ add_configure_property (project, config, GBF_MKFILE_CONFIG_ENTRY,
+ _("Includes:"), NULL, "includes",
+ table, 1);
+ /* Install directories */
+ value = gbf_mkfile_config_mapping_lookup (config, "installdirs");
+ if (value)
+ {
+ GtkWidget *table2, *frame, *lab;
+ char *text;
+ frame = gtk_frame_new ("");
+
+ lab = gtk_frame_get_label_widget (GTK_FRAME(frame));
+ text = g_strdup_printf ("<b>%s</b>", _("Install directories:"));
+ gtk_label_set_markup (GTK_LABEL(lab), text);
+ g_free (text);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame),
+ GTK_SHADOW_NONE);
+ gtk_widget_show (frame);
+ gtk_table_attach (GTK_TABLE (table), frame, 0, 2, 2, 3,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 3);
+ table2 = gtk_table_new (0, 0, FALSE);
+ gtk_widget_show (table2);
+ gtk_container_set_border_width (GTK_CONTAINER (table2), 5);
+ gtk_container_add (GTK_CONTAINER(frame), table2);
+ gbf_mkfile_config_mapping_foreach (value->mapping,
+ recursive_config_foreach_cb,
+ table2);
+ }
+ gtk_widget_show_all (table);
+ gbf_project_group_free (group);
+ return table;
+}
+
+static void
+on_target_widget_destroy (GtkWidget *wid, GtkWidget *table)
+{
+ GError *err = NULL;
+
+ GbfMkfileProject *project = g_object_get_data (G_OBJECT (table), "__project");
+ GbfMkfileConfigMapping *new_config = g_object_get_data (G_OBJECT (table), "__config");
+ const gchar *target_id = g_object_get_data (G_OBJECT (table), "__target_id");
+ gbf_mkfile_project_set_target_config (project, target_id, new_config, &err);
+ if (err)
+ {
+ g_warning ("%s", err->message);
+ g_error_free (err);
+ }
+ g_object_unref (table);
+}
+
+GtkWidget*
+gbf_mkfile_properties_get_target_widget (GbfMkfileProject *project,
+ const gchar *target_id, GError **error)
+{
+ GbfProjectGroup *group;
+ GbfMkfileConfigMapping *group_config;
+ GbfProjectTarget *target;
+ GbfMkfileConfigMapping *config;
+ GbfMkfileConfigValue *value;
+ GbfMkfileConfigMapping *installdirs;
+ GbfMkfileConfigValue *installdirs_val, *dir_val;
+ GtkWidget *table;
+ GError *err = NULL;
+
+ g_return_val_if_fail (GBF_IS_MKFILE_PROJECT (project), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ target = gbf_project_get_target (GBF_PROJECT (project), target_id, &err);
+ if (err)
+ {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+ config = gbf_mkfile_project_get_target_config (project, target_id, &err);
+ if (err)
+ {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+ g_return_val_if_fail (target != NULL, NULL);
+ g_return_val_if_fail (config != NULL, NULL);
+
+ group = gbf_project_get_group (GBF_PROJECT (project),
+ target->group_id, NULL);
+ group_config = gbf_mkfile_project_get_group_config (project,
+ target->group_id,
+ NULL);
+
+ table = gtk_table_new (7, 2, FALSE);
+ g_object_ref (table);
+ g_object_set_data (G_OBJECT (table), "__project", project);
+ g_object_set_data_full (G_OBJECT (table), "__config", config,
+ (GDestroyNotify)gbf_mkfile_config_mapping_destroy);
+ g_object_set_data_full (G_OBJECT (table), "__target_id",
+ g_strdup (target_id),
+ (GDestroyNotify)g_free);
+ g_signal_connect (table, "destroy",
+ G_CALLBACK (on_target_widget_destroy), table);
+
+ /* Target name */
+ add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+ _("Target name:"), target->name, NULL, table, 0);
+ /* Target type */
+ add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+ _("Type:"),
+ gbf_project_name_for_type (GBF_PROJECT (project),
+ target->type),
+ NULL, table, 1);
+ /* Target group */
+ add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+ _("Group:"), group->name,
+ NULL, table, 2);
+
+ /* Target primary */
+ /* FIXME: Target config 'installdir' actually stores target primary,
+ * and not what it really seems to mean, i.e the primary prefix it
+ * belongs to. The actual install directory of a target is the
+ * install directory of primary prefix it belongs to and is
+ * configured in group properties.
+ */
+ value = gbf_mkfile_config_mapping_lookup (config, "installdir");
+ installdirs_val = gbf_mkfile_config_mapping_lookup (group_config,
+ "installdirs");
+ if (installdirs_val)
+ installdirs = gbf_mkfile_config_value_get_mapping (installdirs_val);
+
+ if (!value || !installdirs_val) {
+ add_configure_property (project, config, GBF_MKFILE_CONFIG_LABEL,
+ _("Install directory:"), NULL, "installdir",
+ table, 3);
+ } else {
+ const gchar *primary_prefix;
+ gchar *installdir;
+
+ primary_prefix = gbf_mkfile_config_value_get_string (value);
+ installdirs = gbf_mkfile_config_value_get_mapping (installdirs_val);
+ dir_val = gbf_mkfile_config_mapping_lookup (installdirs,
+ primary_prefix);
+ if (dir_val)
+ {
+ installdir = g_strconcat (primary_prefix, " = ",
+ gbf_mkfile_config_value_get_string (dir_val),
+ NULL);
+ add_configure_property (project, config,
+ GBF_MKFILE_CONFIG_LABEL,
+ _("Install directory:"),
+ installdir, NULL,
+ table, 3);
+ g_free (installdir);
+ } else {
+ add_configure_property (project, config,
+ GBF_MKFILE_CONFIG_LABEL,
+ _("Install directory:"),
+ NULL, "installdir",
+ table, 3);
+ }
+ }
+
+ if (target->type && (strcmp (target->type, "program") == 0 ||
+ strcmp (target->type, "shared_lib") == 0 ||
+ strcmp (target->type, "static_lib") == 0)) {
+ /* LDFLAGS */
+ add_configure_property (project, config,
+ GBF_MKFILE_CONFIG_ENTRY,
+ _("Linker flags:"), NULL,
+ "ldflags", table, 4);
+
+ /* LDADD */
+ add_configure_property (project, config,
+ GBF_MKFILE_CONFIG_ENTRY,
+ _("Libraries:"), NULL,
+ "ldadd", table, 5);
+
+ /* DEPENDENCIES */
+ add_configure_property (project, config,
+ GBF_MKFILE_CONFIG_ENTRY,
+ _("Dependencies:"), NULL,
+ "explicit_deps", table, 6);
+ }
+ gtk_widget_show_all (table);
+ gbf_project_target_free (target);
+ return table;
+}
Added: trunk/plugins/gbf-mkfile/gbf-mkfile-properties.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile-properties.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,42 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf-mkfile-properties.h
+ *
+ * Copyright (C) 2005 Eric Greveson
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Eric Greveson
+ * Based on the Autotools GBF backend (libgbf-am) by
+ * JP Rosevear
+ * Dave Camp
+ * Naba Kumar
+ * Gustavo GirÃldez
+ */
+
+#ifndef _GBF_MKFILE_PROPERTIES_H_
+#define _GBF_MKFILE_PROPERTIES_H_
+
+#include "gbf-mkfile-project.h"
+
+GtkWidget *gbf_mkfile_properties_get_widget (GbfMkfileProject *project, GError **error);
+GtkWidget *gbf_mkfile_properties_get_group_widget (GbfMkfileProject *project,
+ const gchar *group_id,
+ GError **error);
+GtkWidget *gbf_mkfile_properties_get_target_widget (GbfMkfileProject *project,
+ const gchar *target_id,
+ GError **error);
+
+#endif /* _GBF_MKFILE_PROPERTIES_H_ */
Added: trunk/plugins/gbf-mkfile/gbf-mkfile.plugin.in
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/gbf-mkfile.plugin.in Wed Dec 31 11:55:52 2008
@@ -0,0 +1,11 @@
+[Anjuta Plugin]
+_Name=Makefile backend
+_Description=Makefile backend for project manager
+Location=gbf-mkfile:GbfMkfilePlugin
+Icon=gbf-mkfile-plugin-48.png
+UserActivatable=no
+Interfaces=IAnjutaProjectBackend
+Dependencies=anjuta-project-manager:ProjectManagerPlugin
+
+[Project]
+Supported-Project-Types=make
Added: trunk/plugins/gbf-mkfile/plugin.c
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/plugin.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,123 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ plugin.c
+ Copyright (C) 2008 SÃbastien Granjoux
+
+ 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 2 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#define DEBUG
+#include <config.h>
+#include <libanjuta/anjuta-debug.h>
+#include <libanjuta/gbf-project.h>
+#include <libanjuta/interfaces/ianjuta-project-backend.h>
+
+#include "plugin.h"
+#include "gbf-mkfile-project.h"
+
+
+#define ICON_FILE "gbf-mkfile-plugin-48.png"
+
+/* AnjutaPlugin functions
+ *---------------------------------------------------------------------------*/
+
+static gboolean
+activate_plugin (AnjutaPlugin *plugin)
+{
+ DEBUG_PRINT ("GbfMkfilePlugin: Activating Gnome build makefile backend Plugin ...");
+
+ return TRUE;
+}
+
+static gboolean
+deactivate_plugin (AnjutaPlugin *plugin)
+{
+ DEBUG_PRINT ("GbfMkfilePlugin: Deactivating Gnome build makefile backend Plugin ...");
+ return TRUE;
+}
+
+
+/* IAnjutaProjectBackend implementation
+ *---------------------------------------------------------------------------*/
+
+static GbfProject*
+iproject_backend_new_project (IAnjutaProjectBackend* backend, GError** err)
+{
+ GbfProject *project;
+
+ project = gbf_mkfile_project_new ();
+
+ return project;
+}
+
+static void
+iproject_backend_iface_init(IAnjutaProjectBackendIface *iface)
+{
+ iface->new_project = iproject_backend_new_project;
+}
+
+/* GObject functions
+ *---------------------------------------------------------------------------*/
+
+/* Used in dispose and finalize */
+static gpointer parent_class;
+
+static void
+gbf_mkfile_plugin_instance_init (GObject *obj)
+{
+}
+
+/* dispose is used to unref object created with instance_init */
+
+static void
+dispose (GObject *obj)
+{
+ G_OBJECT_CLASS (parent_class)->dispose (obj);
+}
+
+/* finalize used to free object created with instance init */
+
+static void
+finalize (GObject *obj)
+{
+ G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static void
+gbf_mkfile_plugin_class_init (GObjectClass *klass)
+{
+ AnjutaPluginClass *plugin_class = ANJUTA_PLUGIN_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ plugin_class->activate = activate_plugin;
+ plugin_class->deactivate = deactivate_plugin;
+ klass->dispose = dispose;
+ klass->finalize = finalize;
+}
+
+/* AnjutaPlugin declaration
+ *---------------------------------------------------------------------------*/
+
+ANJUTA_PLUGIN_BEGIN (GbfMkfilePlugin, gbf_mkfile_plugin);
+ANJUTA_PLUGIN_ADD_INTERFACE (iproject_backend, IANJUTA_TYPE_PROJECT_BACKEND);
+ANJUTA_PLUGIN_END;
+
+G_MODULE_EXPORT void
+anjuta_glue_register_components (GTypeModule *module)
+{
+ gbf_mkfile_plugin_get_type (module);
+ gbf_mkfile_project_get_type (module);
+}
Added: trunk/plugins/gbf-mkfile/plugin.h
==============================================================================
--- (empty file)
+++ trunk/plugins/gbf-mkfile/plugin.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,47 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ plugin.h
+ Copyright (C) 2008 SÃbastien Granjoux
+
+ 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 2 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef __PLUGIN_H__
+#define __PLUGIN_H__
+
+#include <libanjuta/anjuta-plugin.h>
+
+extern GType gbf_mkfile_plugin_get_type (GTypeModule *module);
+#define GBF_TYPE_PLUGIN_MKFILE (gbf_mkfile_plugin_get_type (NULL))
+#define GBF_PLUGIN_MKFILE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GBF_TYPE_PLUGIN_MKFILE, GbfMkfilePlugin))
+#define GBF_PLUGIN_MKFILE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GBF_TYPE_PLUGIN_MKFILE, GbfMkfilePluginClass))
+#define GBF_IS_PLUGIN_MKFILE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GBF_TYPE_PLUGIN_MKFILE))
+#define GBF_IS_PLUGIN_MKFILE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GBF_TYPE_PLUGIN_MKFILE))
+#define GBF_PLUGIN_MKFILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GBF_TYPE_PLUGIN_MKFILE, GbfMkfilePluginClass))
+
+typedef struct _GbfMkfilePlugin GbfMkfilePlugin;
+typedef struct _GbfMkfilePluginClass GbfMkfilePluginClass;
+
+struct _GbfMkfilePlugin
+{
+ AnjutaPlugin parent;
+};
+
+struct _GbfMkfilePluginClass
+{
+ AnjutaPluginClass parent_class;
+};
+
+#endif
Modified: trunk/plugins/project-import/project-import.c
==============================================================================
--- trunk/plugins/project-import/project-import.c (original)
+++ trunk/plugins/project-import/project-import.c Wed Dec 31 11:55:52 2008
@@ -19,12 +19,12 @@
#include "project-import.h"
#include <libanjuta/interfaces/ianjuta-file-loader.h>
+#include <libanjuta/interfaces/ianjuta-project-backend.h>
+#include <libanjuta/gbf-project.h>
#include <libanjuta/anjuta-debug.h>
#include <config.h>
-#include <gbf/gbf-backend.h>
-
#define AM_PROJECT_FILE PACKAGE_DATA_DIR"/project/terminal/project.anjuta"
#define MKFILE_PROJECT_FILE PACKAGE_DATA_DIR"/project/mkfile/project.anjuta"
@@ -51,9 +51,10 @@
static void
on_import_next(GtkAssistant *assistant, GtkWidget *page, ProjectImport *pi)
{
- GSList* l;
- GbfBackend* backend = NULL;
- GbfProject* proj;
+ AnjutaPluginManager *plugin_manager;
+ GList *descs = NULL;
+ GList *desc;
+ AnjutaPluginDescription *backend;
if (page != pi->import_finish)
return;
@@ -67,41 +68,48 @@
return;
}
- gbf_backend_init();
-
- for (l = gbf_backend_get_backends (); l; l = l->next) {
- backend = l->data;
- if (!backend)
- {
- g_warning("Backend appears empty!");
- continue;
- }
+ plugin_manager = anjuta_shell_get_plugin_manager (ANJUTA_PLUGIN(pi)->shell, NULL);
+ descs = anjuta_plugin_manager_query (plugin_manager,
+ "Anjuta Plugin",
+ "Interfaces",
+ "IAnjutaProjectBackend",
+ NULL);
+ for (desc = g_list_first (descs); desc != NULL; desc = g_list_next (desc)) {
+ IAnjutaProjectBackend *plugin;
+ gchar *location = NULL;
+ GbfProject* proj;
+ backend = (AnjutaPluginDescription *)desc->data;
+ anjuta_plugin_description_get_string (backend, "Anjuta Plugin", "Location", &location);
+ plugin = (IAnjutaProjectBackend *)anjuta_plugin_manager_get_plugin_by_id (plugin_manager, location);
+ g_free (location);
+
/* Probe the backend to find out if the project directory is OK */
/* If probe() returns TRUE then we have a valid backend */
- proj = gbf_backend_new_project(backend->id);
+ proj= ianjuta_project_backend_new_project (plugin, NULL);
if (proj)
{
- if (gbf_project_probe(proj, path, NULL))
+ if (gbf_project_probe (proj, path, NULL))
{
/* This is a valid backend for this root directory */
/* FIXME: Possibility of more than one valid backend? */
- g_object_unref(proj);
break;
}
- g_object_unref(proj);
+ g_object_unref (proj);
}
+ plugin = NULL;
backend = NULL;
}
-
+ g_list_free (descs);
+
if (!backend)
{
gchar* message_text =
g_strdup_printf(_("Could not find a valid project backend for the "
"directory given (%s). Please select a different "
"directory, or try upgrading to a newer version of "
- "the Gnome Build Framework."), path);
+ "Anjuta."), path);
GtkDialog* message =
GTK_DIALOG(gtk_message_dialog_new(GTK_WINDOW(pi->assistant),
@@ -125,11 +133,17 @@
}
gchar* summary;
+ gchar* type;
+ if (!anjuta_plugin_description_get_string (backend, "Project", "Supported-Project-Types", &type))
+ {
+ type = g_strdup ("unknown");
+ }
summary = g_strdup_printf(_("Project name: %s\n"
"Project type: %s\n"
"Project path: %s\n"),
- name, backend->name, path);
+ name, type, path);
+
gtk_label_set_text (GTK_LABEL (pi->import_finish),
summary);
@@ -142,7 +156,7 @@
if (pi->backend_id)
g_free(pi->backend_id);
- pi->backend_id = g_strdup(backend->id);
+ pi->backend_id = type;
g_free (path);
}
@@ -402,9 +416,9 @@
take a default project file. */
GFile* source_file;
- if (!strcmp (pi->backend_id, "gbf-am:GbfAmProject"))
+ if (!strcmp (pi->backend_id, "automake"))
source_file = g_file_new_for_path (AM_PROJECT_FILE);
- else if (!strcmp (pi->backend_id, "gbf-mkfile:GbfMkfileProject"))
+ else if (!strcmp (pi->backend_id, "make"))
source_file = g_file_new_for_path (MKFILE_PROJECT_FILE);
else
{
Modified: trunk/plugins/project-manager/Makefile.am
==============================================================================
--- trunk/plugins/project-manager/Makefile.am (original)
+++ trunk/plugins/project-manager/Makefile.am Wed Dec 31 11:55:52 2008
@@ -2,11 +2,17 @@
project_uidir = $(anjuta_ui_dir)
project_ui_DATA = anjuta-project-manager.ui
+# Plugin glade file
+project_gladedir = $(anjuta_glade_dir)
+project_glade_DATA = create_dialogs.glade
+
# Plugin Icon file
project_pixmapsdir = $(anjuta_image_dir)
project_pixmaps_DATA = \
anjuta-project-manager-plugin.svg \
- anjuta-project-manager-plugin-48.png
+ anjuta-project-manager-plugin-48.png \
+ gbf-build.png \
+ gbf-install.png
# Plugin description file
plugin_in_files = anjuta-project-manager.plugin.in
@@ -19,8 +25,8 @@
AM_CPPFLAGS = \
$(WARN_CFLAGS) \
$(DEPRECATED_FLAGS) \
- $(PLUGIN_GNOMEBUILD_CFLAGS) \
- $(LIBANJUTA_CFLAGS)
+ $(LIBANJUTA_CFLAGS) \
+ -DG_LOG_DOMAIN=\"libanjuta-project-manager\"
# Where to install the plugin
plugindir = $(anjuta_plugin_dir)
@@ -31,13 +37,20 @@
# Plugin sources
libanjuta_project_manager_la_SOURCES = \
plugin.c \
- plugin.h
+ plugin.h \
+ gbf-tree-data.h \
+ gbf-tree-data.c \
+ gbf-project-model.h \
+ gbf-project-model.c \
+ gbf-project-view.h \
+ gbf-project-view.c \
+ gbf-project-util.h \
+ gbf-project-util.c
libanjuta_project_manager_la_LDFLAGS = $(ANJUTA_PLUGIN_LDFLAGS)
# Plugin dependencies
libanjuta_project_manager_la_LIBADD = \
- $(PLUGIN_GNOMEBUILD_LIBS) \
$(GNOME_UI_LIBS) \
$(LIBANJUTA_LIBS)
@@ -46,3 +59,7 @@
$(project_plugin_DATA) \
$(project_ui_DATA) \
$(project_pixmaps_DATA)
+
+DISTCLEANFILES = \
+ $(project_plugin_DATA) \
+ $(project_in_files)
Added: trunk/plugins/project-manager/create_dialogs.glade
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/create_dialogs.glade Wed Dec 31 11:55:52 2008
@@ -0,0 +1,468 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
+<glade-interface>
+ <widget class="GtkDialog" id="new_group_dialog">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">New Group</property>
+ <property name="modal">True</property>
+ <property name="default_width">400</property>
+ <property name="default_height">450</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="border_width">5</property>
+ <property name="spacing">12</property>
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Group name:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">group_name_entry</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="group_name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">True</property>
+ <signal name="changed" handler="on_group_name_entry_changed"/>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Specify _where to create the group:</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="groups_ph">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <child>
+ <widget class="Custom" id="groups_view">
+ <property name="visible">True</property>
+ <property name="string1">GbfProjectView</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="cancel_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="ok_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="label">gtk-add</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="new_target_dialog">
+ <property name="visible">True</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">New Target</property>
+ <property name="modal">True</property>
+ <property name="default_width">400</property>
+ <property name="default_height">450</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkVBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="border_width">5</property>
+ <property name="spacing">12</property>
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="Custom" id="target_type_combo">
+ <property name="visible">True</property>
+ <property name="string1">GtkComboBox</property>
+ <accessibility>
+ <atkproperty name="AtkObject::accessible_name" translatable="yes">TargetTypes</atkproperty>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Target _type:</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="target_name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Target _name:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">target_name_entry</property>
+ </widget>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Specify _where to create the target:</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="groups_ph">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <child>
+ <widget class="Custom" id="groups_view">
+ <property name="visible">True</property>
+ <property name="string1">GbfProjectView</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="button2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="ok_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="label">gtk-add</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="add_source_dialog">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Add Source</property>
+ <property name="modal">True</property>
+ <property name="default_width">400</property>
+ <property name="default_height">550</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="border_width">5</property>
+ <property name="spacing">12</property>
+ <child>
+ <widget class="GtkVBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Select the _target for the new source files:</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="targets_ph">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <child>
+ <widget class="Custom" id="targets_view">
+ <property name="visible">True</property>
+ <property name="string1">GbfProjectView</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <child>
+ <widget class="GtkTreeView" id="source_file_tree">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="fixed_height_mode">True</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="browse_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Select file to add...</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Source files:</property>
+ </widget>
+ <packing>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="hbuttonbox2">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="button4">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="ok_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="label">gtk-add</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
Added: trunk/plugins/project-manager/gbf-project-model.c
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-model.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,957 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2002 Dave Camp
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Dave Camp <dave ximian com>
+ * Gustavo Giráez <gustavo giraldez gmx net>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libgnomevfs/gnome-vfs-uri.h>
+
+#include "gbf-project-model.h"
+
+
+struct _GbfProjectModelPrivate {
+ GbfProject *proj;
+ gulong project_updated_handler;
+
+ GtkTreeRowReference *root_row;
+ GList *shortcuts;
+
+ GbfTreeData *empty_node;
+};
+
+enum {
+ PROP_NONE,
+ PROP_PROJECT
+};
+
+
+/* Function prototypes ------------- */
+
+static void gbf_project_model_class_init (GbfProjectModelClass *klass);
+static void gbf_project_model_instance_init (GbfProjectModel *tree);
+static void gbf_project_model_drag_source_init (GtkTreeDragSourceIface *iface);
+static void gbf_project_model_drag_dest_init (GtkTreeDragDestIface *iface);
+
+static void load_project (GbfProjectModel *model,
+ GbfProject *proj);
+static void insert_empty_node (GbfProjectModel *model);
+static void unload_project (GbfProjectModel *model);
+
+static gint default_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data);
+
+/* DND functions */
+
+static gboolean row_draggable (GtkTreeDragSource *drag_source,
+ GtkTreePath *path);
+
+static gboolean drag_data_delete (GtkTreeDragSource *drag_source,
+ GtkTreePath *path);
+
+static gboolean drag_data_received (GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest,
+ GtkSelectionData *selection_data);
+
+static gboolean row_drop_possible (GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest_path,
+ GtkSelectionData *selection_data);
+
+
+static GtkTreeStoreClass *parent_class = NULL;
+
+
+/* Implementation ---------------- */
+
+/* Type & interfaces initialization */
+
+static void
+gbf_project_model_drag_source_init (GtkTreeDragSourceIface *iface)
+{
+ iface->row_draggable = row_draggable;
+ iface->drag_data_delete = drag_data_delete;
+}
+
+static void
+gbf_project_model_drag_dest_init (GtkTreeDragDestIface *iface)
+{
+ iface->drag_data_received = drag_data_received;
+ iface->row_drop_possible = row_drop_possible;
+}
+
+static void
+gbf_project_model_class_init_trampoline (gpointer klass,
+ gpointer data)
+{
+ parent_class = g_type_class_ref (GTK_TYPE_TREE_STORE);
+ gbf_project_model_class_init (klass);
+}
+
+GType
+gbf_project_model_get_type (void)
+{
+ static GType object_type = 0;
+ if (object_type == 0) {
+ static const GTypeInfo object_info = {
+ sizeof (GbfProjectModelClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ gbf_project_model_class_init_trampoline,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (GbfProjectModel),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gbf_project_model_instance_init
+ };
+ static const GInterfaceInfo drag_source_info = {
+ (GInterfaceInitFunc) gbf_project_model_drag_source_init,
+ NULL,
+ NULL
+ };
+ static const GInterfaceInfo drag_dest_info = {
+ (GInterfaceInitFunc) gbf_project_model_drag_dest_init,
+ NULL,
+ NULL
+ };
+
+ object_type = g_type_register_static (
+ GTK_TYPE_TREE_STORE, "GbfProjectModel",
+ &object_info, 0);
+
+ g_type_add_interface_static (object_type,
+ GTK_TYPE_TREE_DRAG_SOURCE,
+ &drag_source_info);
+
+ g_type_add_interface_static (object_type,
+ GTK_TYPE_TREE_DRAG_DEST,
+ &drag_dest_info);
+ }
+ return object_type;
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbfProjectModel *model = GBF_PROJECT_MODEL (object);
+
+ switch (prop_id) {
+ case PROP_PROJECT:
+ g_value_set_pointer (value, model->priv->proj);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbfProjectModel *model = GBF_PROJECT_MODEL (object);
+
+ switch (prop_id) {
+ case PROP_PROJECT:
+ gbf_project_model_set_project (model, g_value_get_pointer (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *obj)
+{
+ GbfProjectModel *model = GBF_PROJECT_MODEL (obj);
+
+ if (model->priv->empty_node) {
+ gbf_tree_data_free (model->priv->empty_node);
+ model->priv->empty_node = NULL;
+ }
+
+ if (model->priv->proj) {
+ unload_project (model);
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static void
+finalize (GObject *obj)
+{
+ GbfProjectModel *model = GBF_PROJECT_MODEL (obj);
+
+ g_free (model->priv);
+
+ G_OBJECT_CLASS (parent_class)->dispose (obj);
+}
+
+static void
+gbf_project_model_class_init (GbfProjectModelClass *klass)
+{
+ parent_class = g_type_class_peek_parent (klass);
+
+ G_OBJECT_CLASS (klass)->dispose = dispose;
+ G_OBJECT_CLASS (klass)->finalize = finalize;
+ G_OBJECT_CLASS (klass)->get_property = get_property;
+ G_OBJECT_CLASS (klass)->set_property = set_property;
+
+ g_object_class_install_property
+ (G_OBJECT_CLASS (klass), PROP_PROJECT,
+ g_param_spec_pointer ("project",
+ _("Project"),
+ _("GbfProject Object"),
+ G_PARAM_READWRITE));
+}
+
+static void
+gbf_project_model_instance_init (GbfProjectModel *model)
+{
+ static GType types [GBF_PROJECT_MODEL_NUM_COLUMNS];
+
+ types [GBF_PROJECT_MODEL_COLUMN_DATA] = GBF_TYPE_TREE_DATA;
+
+ gtk_tree_store_set_column_types (GTK_TREE_STORE (model),
+ GBF_PROJECT_MODEL_NUM_COLUMNS,
+ types);
+
+ model->priv = g_new0 (GbfProjectModelPrivate, 1);
+
+ model->priv->empty_node = gbf_tree_data_new_string (_("No project loaded"));
+ /* sorting function */
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model),
+ default_sort_func,
+ NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
+ GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+ GTK_SORT_ASCENDING);
+
+ insert_empty_node (model);
+}
+
+/* Model data functions ------------ */
+
+static gint
+default_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data)
+{
+ GbfTreeData *data_a, *data_b;
+ gint retval = 0;
+
+ gtk_tree_model_get (model, iter_a,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data_a,
+ -1);
+ gtk_tree_model_get (model, iter_b,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data_b,
+ -1);
+
+ if (data_a->is_shortcut && data_b->is_shortcut) {
+ GList *l;
+
+ /* special case: the order of shortcuts is
+ * user customizable */
+ for (l = GBF_PROJECT_MODEL (model)->priv->shortcuts; l; l = l->next) {
+ if (!strcmp (l->data, data_a->id)) {
+ /* a comes first */
+ retval = -1;
+ break;
+ }
+ else if (!strcmp (l->data, data_b->id)) {
+ /* b comes first */
+ retval = 1;
+ break;
+ }
+ }
+
+ } else if (data_a->is_shortcut && !data_b->is_shortcut) {
+ retval = -1;
+
+ } else if (!data_a->is_shortcut && data_b->is_shortcut) {
+ retval = 1;
+
+ } else if (data_a->type == data_b->type) {
+ retval = strcmp (data_a->name, data_b->name);
+
+ } else {
+ /* assume a->b and check for the opposite cases */
+ retval = -1;
+ if (data_a->type == GBF_TREE_NODE_TARGET &&
+ data_b->type == GBF_TREE_NODE_GROUP) {
+ retval = 1;
+ }
+ }
+
+ gbf_tree_data_free (data_a);
+ gbf_tree_data_free (data_b);
+
+ return retval;
+}
+
+
+static void
+add_source (GbfProjectModel *model,
+ const gchar *source_id,
+ GtkTreeIter *parent)
+{
+ GbfProjectTargetSource *source;
+ GtkTreeIter iter;
+ GbfTreeData *data;
+
+ source = gbf_project_get_source (model->priv->proj, source_id, NULL);
+ if (!source)
+ return;
+
+ data = gbf_tree_data_new_source (model->priv->proj, source);
+ gtk_tree_store_append (GTK_TREE_STORE (model), &iter, parent);
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, data,
+ -1);
+ gbf_tree_data_free (data);
+
+ gbf_project_target_source_free (source);
+}
+
+static GtkTreePath *
+find_shortcut (GbfProjectModel *model, const gchar *target_id)
+{
+ GList *l;
+ gint i;
+
+ for (l = model->priv->shortcuts, i = 0; l; l = l->next, i++) {
+ if (!strcmp (target_id, l->data))
+ return gtk_tree_path_new_from_indices (i, -1);
+ }
+ return NULL;
+}
+
+static void
+remove_shortcut (GbfProjectModel *model, const gchar *target_id)
+{
+ GList *l;
+
+ for (l = model->priv->shortcuts; l; l = l->next) {
+ if (!strcmp (target_id, l->data)) {
+ g_free (l->data);
+ model->priv->shortcuts = g_list_delete_link (
+ model->priv->shortcuts, l);
+ break;
+ }
+ }
+}
+
+static void
+add_target_shortcut (GbfProjectModel *model,
+ const gchar *target_id,
+ GtkTreePath *before_path)
+{
+ GList *l;
+ GtkTreeIter iter, sibling;
+ GbfProjectTarget *target;
+ GtkTreePath *root_path, *old_path;
+ gint *path_indices, i;
+ GbfTreeData *data;
+
+ target = gbf_project_get_target (model->priv->proj, target_id, NULL);
+ if (!target)
+ return;
+
+ root_path = gtk_tree_row_reference_get_path (model->priv->root_row);
+ /* check before_path */
+ if (!before_path ||
+ gtk_tree_path_get_depth (before_path) > 1 ||
+ gtk_tree_path_compare (before_path, root_path) > 0) {
+ before_path = root_path;
+ }
+
+ /* get the tree iter for the row before which to insert the shortcut */
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &sibling, before_path)) {
+ gtk_tree_path_free (root_path);
+ return;
+ }
+
+ path_indices = gtk_tree_path_get_indices (before_path);
+ i = path_indices [0];
+
+ /* remove the old shortcut to make sorting actually work */
+ old_path = find_shortcut (model, target_id);
+ if (old_path) {
+ remove_shortcut (model, target_id);
+ if (gtk_tree_path_compare (old_path, before_path) < 0) {
+ /* adjust shortcut insert position if the old
+ * index was before the new site */
+ i--;
+ }
+ gtk_tree_path_free (old_path);
+ }
+
+ /* add entry to the shortcut list */
+ model->priv->shortcuts = g_list_insert (model->priv->shortcuts,
+ g_strdup (target->id), i);
+
+ data = gbf_tree_data_new_target (model->priv->proj, target);
+ data->is_shortcut = TRUE;
+ gtk_tree_store_insert_before (GTK_TREE_STORE (model), &iter, NULL, &sibling);
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, data,
+ -1);
+ gbf_tree_data_free (data);
+
+ /* add sources */
+ for (l = target->sources; l; l = l->next)
+ add_source (model, l->data, &iter);
+
+ gtk_tree_path_free (root_path);
+ gbf_project_target_free (target);
+}
+
+static void
+add_target (GbfProjectModel *model,
+ const gchar *target_id,
+ GtkTreeIter *parent)
+{
+ GbfProjectTarget *target;
+ GList *l;
+ GtkTreeIter iter;
+ GbfTreeData *data;
+
+ target = gbf_project_get_target (model->priv->proj, target_id, NULL);
+ if (!target)
+ return;
+
+ data = gbf_tree_data_new_target (model->priv->proj, target);
+ gtk_tree_store_append (GTK_TREE_STORE (model), &iter, parent);
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, data,
+ -1);
+ gbf_tree_data_free (data);
+
+ /* add sources */
+ for (l = target->sources; l; l = l->next)
+ add_source (model, l->data, &iter);
+
+ /* add a shortcut to the target if the target's type is a primary */
+ /* FIXME: this shouldn't be here. We would rather provide a
+ * set of public functions to add/remove shortcuts to save
+ * this information in the project metadata (when that's
+ * implemented) */
+ if (!strcmp (target->type, "program") ||
+ !strcmp (target->type, "shared_lib") ||
+ !strcmp (target->type, "static_lib") ||
+ !strcmp (target->type, "java") ||
+ !strcmp (target->type, "python")) {
+ add_target_shortcut (model, target->id, NULL);
+ }
+
+ gbf_project_target_free (target);
+}
+
+static void
+update_target (GbfProjectModel *model, const gchar *target_id, GtkTreeIter *iter)
+{
+ GtkTreeModel *tree_model;
+ GbfProjectTarget *target;
+ GtkTreeIter child;
+ GList *node;
+
+ tree_model = GTK_TREE_MODEL (model);
+ target = gbf_project_get_target (model->priv->proj, target_id, NULL);
+ if (!target)
+ return;
+
+ /* update target data here */
+
+ /* walk the tree target */
+ if (gtk_tree_model_iter_children (tree_model, &child, iter)) {
+ GbfTreeData *data;
+ gboolean valid = TRUE;
+
+ while (valid) {
+ gtk_tree_model_get (tree_model, &child,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+ -1);
+
+ /* find the iterating id in the target's sources */
+ if (data->id) {
+ node = g_list_find_custom (target->sources,
+ data->id, (GCompareFunc) strcmp);
+ if (node) {
+ target->sources = g_list_delete_link (target->sources, node);
+ valid = gtk_tree_model_iter_next (tree_model, &child);
+ } else {
+ valid = gtk_tree_store_remove (GTK_TREE_STORE (model), &child);
+ }
+ gbf_tree_data_free (data);
+ }
+ }
+ }
+
+ /* add the remaining sources */
+ for (node = target->sources; node; node = node->next)
+ add_source (model, node->data, iter);
+
+ gbf_project_target_free (target);
+}
+
+static void
+add_target_group (GbfProjectModel *model,
+ const gchar *group_id,
+ GtkTreeIter *parent)
+{
+ GbfProjectGroup *group;
+ GtkTreeIter iter;
+ GList *l;
+ GbfTreeData *data;
+
+ group = gbf_project_get_group (model->priv->proj, group_id, NULL);
+ if (!group)
+ return;
+
+ data = gbf_tree_data_new_group (model->priv->proj, group);
+ gtk_tree_store_append (GTK_TREE_STORE (model), &iter, parent);
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, data,
+ -1);
+ gbf_tree_data_free (data);
+
+ /* create root reference */
+ if (parent == NULL) {
+ GtkTreePath *root_path;
+
+ root_path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ model->priv->root_row = gtk_tree_row_reference_new (
+ GTK_TREE_MODEL (model), root_path);
+ gtk_tree_path_free (root_path);
+ }
+
+ /* add groups ... */
+ for (l = group->groups; l; l = l->next)
+ add_target_group (model, l->data, &iter);
+
+ /* ... and targets */
+ for (l = group->targets; l; l = l->next)
+ add_target (model, l->data, &iter);
+
+ gbf_project_group_free (group);
+}
+
+static void
+update_group (GbfProjectModel *model, const gchar *group_id, GtkTreeIter *iter)
+{
+ GtkTreeModel *tree_model;
+ GbfProjectGroup *group;
+ GtkTreeIter child;
+ GList *node;
+
+ tree_model = GTK_TREE_MODEL (model);
+ group = gbf_project_get_group (model->priv->proj, group_id, NULL);
+
+ /* update group data. nothing to do here */
+
+ /* walk the tree group */
+ /* group can be NULL, but we iterate anyway to remove any
+ * shortcuts the old group could have had */
+ if (gtk_tree_model_iter_children (tree_model, &child, iter)) {
+ GbfTreeData *data;
+ gboolean valid = TRUE;
+
+ while (valid) {
+ gboolean remove_child = FALSE;
+
+ gtk_tree_model_get (tree_model, &child,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+ -1);
+
+ /* find the iterating id in the group's children */
+ if (data->type == GBF_TREE_NODE_GROUP) {
+ /* update recursively */
+ update_group (model, data->id, &child);
+ if (group && (node = g_list_find_custom (group->groups, data->id,
+ (GCompareFunc) strcmp))) {
+ group->groups = g_list_delete_link (group->groups, node);
+ } else {
+ remove_child = TRUE;
+ }
+
+ } else if (data->type == GBF_TREE_NODE_TARGET) {
+ GtkTreePath *shortcut;
+
+ if (group && (node = g_list_find_custom (group->targets,
+ data->id,
+ (GCompareFunc) strcmp))) {
+ group->targets = g_list_delete_link (group->targets, node);
+ /* update recursively */
+ update_target (model, data->id, &child);
+ } else {
+ remove_child = TRUE;
+ }
+
+ /* remove or update the shortcut if it previously existed */
+ shortcut = find_shortcut (model, data->id);
+ if (shortcut) {
+ GtkTreeIter tmp;
+
+ if (remove_child)
+ remove_shortcut (model, data->id);
+
+ if (gtk_tree_model_get_iter (tree_model, &tmp, shortcut)) {
+ if (remove_child)
+ gtk_tree_store_remove (GTK_TREE_STORE (model), &tmp);
+ else
+ update_target (model, data->id, &tmp);
+ }
+ gtk_tree_path_free (shortcut);
+ }
+ }
+
+ gbf_tree_data_free (data);
+ if (remove_child)
+ valid = gtk_tree_store_remove (GTK_TREE_STORE (model), &child);
+ else
+ valid = gtk_tree_model_iter_next (tree_model, &child);
+ };
+ }
+
+ if (group) {
+ /* add the remaining targets and groups */
+ for (node = group->groups; node; node = node->next)
+ add_target_group (model, node->data, iter);
+
+ for (node = group->targets; node; node = node->next)
+ add_target (model, node->data, iter);
+
+ gbf_project_group_free (group);
+ }
+}
+
+static void
+project_updated_cb (GbfProject *project, GbfProjectModel *model)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ path = gtk_tree_row_reference_get_path (model->priv->root_row);
+ if (path && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
+ update_group (model, "/", &iter);
+ else
+ add_target_group (model, "/", NULL);
+
+ if (path)
+ gtk_tree_path_free (path);
+}
+
+static void
+load_project (GbfProjectModel *model, GbfProject *proj)
+{
+ model->priv->proj = proj;
+ g_object_ref (proj);
+
+ /* to get rid of the empty node */
+ gtk_tree_store_clear (GTK_TREE_STORE (model));
+
+ add_target_group (model, "/", NULL);
+
+ model->priv->project_updated_handler =
+ g_signal_connect (model->priv->proj, "project-updated",
+ (GCallback) project_updated_cb, model);
+}
+
+static void
+insert_empty_node (GbfProjectModel *model)
+{
+ GtkTreeIter iter;
+
+ gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, model->priv->empty_node,
+ -1);
+}
+
+static void
+unload_project (GbfProjectModel *model)
+{
+ if (model->priv->proj) {
+ gtk_tree_row_reference_free (model->priv->root_row);
+ model->priv->root_row = NULL;
+
+ gtk_tree_store_clear (GTK_TREE_STORE (model));
+
+ g_list_foreach (model->priv->shortcuts, (GFunc) g_free, NULL);
+ g_list_free (model->priv->shortcuts);
+ model->priv->shortcuts = NULL;
+
+ g_signal_handler_disconnect (model->priv->proj,
+ model->priv->project_updated_handler);
+ model->priv->project_updated_handler = 0;
+ g_object_unref (model->priv->proj);
+ model->priv->proj = NULL;
+
+ insert_empty_node (model);
+ }
+}
+
+static gboolean
+recursive_find_id (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GbfTreeNodeType type,
+ const gchar *id)
+{
+ GtkTreeIter tmp;
+ GbfTreeData *data;
+ gboolean retval = FALSE;
+
+ tmp = *iter;
+
+ do {
+ GtkTreeIter child;
+
+ gtk_tree_model_get (model, &tmp,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data, -1);
+ if (data->type == type && !strcmp (id, data->id)) {
+ *iter = tmp;
+ retval = TRUE;
+ }
+ gbf_tree_data_free (data);
+
+ if (gtk_tree_model_iter_children (model, &child, &tmp)) {
+ if (recursive_find_id (model, &child, type, id)) {
+ *iter = child;
+ retval = TRUE;
+ }
+ }
+
+ } while (!retval && gtk_tree_model_iter_next (model, &tmp));
+
+ return retval;
+}
+
+gboolean
+gbf_project_model_find_id (GbfProjectModel *model,
+ GtkTreeIter *iter,
+ GbfTreeNodeType type,
+ const gchar *id)
+{
+ GtkTreePath *root;
+ GtkTreeIter tmp_iter;
+ gboolean retval = FALSE;
+
+ root = gbf_project_model_get_project_root (model);
+ if (!root)
+ return FALSE;
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &tmp_iter, root)) {
+ if (recursive_find_id (GTK_TREE_MODEL (model), &tmp_iter, type, id)) {
+ retval = TRUE;
+ *iter = tmp_iter;
+ }
+ }
+ gtk_tree_path_free (root);
+
+ return retval;
+}
+
+GbfProjectModel *
+gbf_project_model_new (GbfProject *project)
+{
+ return GBF_PROJECT_MODEL (g_object_new (GBF_TYPE_PROJECT_MODEL,
+ "project", project,
+ NULL));
+}
+
+void
+gbf_project_model_set_project (GbfProjectModel *model, GbfProject *project)
+{
+ g_return_if_fail (model != NULL && GBF_IS_PROJECT_MODEL (model));
+ g_return_if_fail (project == NULL || GBF_IS_PROJECT (project));
+
+ if (model->priv->proj)
+ unload_project (model);
+
+ if (project)
+ load_project (model, project);
+}
+
+GbfProject *
+gbf_project_model_get_project (GbfProjectModel *model)
+{
+ g_return_val_if_fail (model != NULL && GBF_IS_PROJECT_MODEL (model), NULL);
+
+ return model->priv->proj;
+}
+
+GtkTreePath *
+gbf_project_model_get_project_root (GbfProjectModel *model)
+{
+ GtkTreePath *path = NULL;
+
+ g_return_val_if_fail (GBF_IS_PROJECT_MODEL (model), NULL);
+
+ if (model->priv->root_row)
+ path = gtk_tree_row_reference_get_path (model->priv->root_row);
+
+ return path;
+}
+
+
+/* DND stuff ------------- */
+
+static gboolean
+row_draggable (GtkTreeDragSource *drag_source, GtkTreePath *path)
+{
+ GtkTreeIter iter;
+ GbfTreeData *data;
+ gboolean retval = FALSE;
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path))
+ return FALSE;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+ -1);
+
+ if (data->is_shortcut) {
+ /* shortcuts can be moved */
+ retval = TRUE;
+
+ } else if (data->type == GBF_TREE_NODE_TARGET) {
+ GtkTreePath *found;
+
+ /* don't allow duplicate shortcuts */
+ found = find_shortcut (GBF_PROJECT_MODEL (drag_source), data->id);
+ if (!found)
+ retval = TRUE;
+ else
+ gtk_tree_path_free (found);
+ }
+ gbf_tree_data_free (data);
+
+ return retval;
+}
+
+static gboolean
+drag_data_delete (GtkTreeDragSource *drag_source, GtkTreePath *path)
+{
+ GtkTreeIter iter;
+ gboolean retval = FALSE;
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source),
+ &iter, path)) {
+ GbfTreeData *data;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+ -1);
+
+ if (data->is_shortcut) {
+ gtk_tree_store_remove (GTK_TREE_STORE (drag_source), &iter);
+ retval = TRUE;
+ }
+ gbf_tree_data_free (data);
+ }
+
+ return retval;
+}
+
+static gboolean
+drag_data_received (GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest,
+ GtkSelectionData *selection_data)
+{
+ GtkTreeModel *src_model = NULL;
+ GtkTreePath *src_path = NULL;
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (GBF_IS_PROJECT_MODEL (drag_dest), FALSE);
+
+ if (gtk_tree_get_row_drag_data (selection_data,
+ &src_model,
+ &src_path) &&
+ src_model == GTK_TREE_MODEL (drag_dest)) {
+
+ GtkTreeIter iter;
+ GbfTreeData *data = NULL;
+
+ if (gtk_tree_model_get_iter (src_model, &iter, src_path)) {
+ gtk_tree_model_get (src_model, &iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+ -1);
+ if (data && data->id && data->type == GBF_TREE_NODE_TARGET) {
+ add_target_shortcut (GBF_PROJECT_MODEL (drag_dest),
+ data->id, dest);
+ retval = TRUE;
+ }
+ gbf_tree_data_free (data);
+ }
+ }
+
+ if (src_path)
+ gtk_tree_path_free (src_path);
+
+ return retval;
+}
+
+static gboolean
+row_drop_possible (GtkTreeDragDest *drag_dest,
+ GtkTreePath *dest_path,
+ GtkSelectionData *selection_data)
+{
+ GtkTreePath *root_path;
+ GbfProjectModel *model;
+ gboolean retval = FALSE;
+ GtkTreeModel *src_model = NULL;
+
+ g_return_val_if_fail (GBF_IS_PROJECT_MODEL (drag_dest), FALSE);
+
+ model = GBF_PROJECT_MODEL (drag_dest);
+
+ if (!gtk_tree_get_row_drag_data (selection_data,
+ &src_model,
+ NULL))
+ return FALSE;
+
+ /* can only drag to ourselves and only new toplevel nodes will
+ * be created */
+ if (src_model == GTK_TREE_MODEL (drag_dest) &&
+ gtk_tree_path_get_depth (dest_path) == 1) {
+ root_path = gtk_tree_row_reference_get_path (model->priv->root_row);
+ if (gtk_tree_path_compare (dest_path, root_path) <= 0) {
+ retval = TRUE;
+ }
+ gtk_tree_path_free (root_path);
+ }
+
+ return retval;
+}
Added: trunk/plugins/project-manager/gbf-project-model.h
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-model.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2002 Dave Camp
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Dave Camp <dave ximian com>
+ */
+
+#ifndef GBF_PROJECT_MODEL_H
+#define GBF_PROJECT_MODEL_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <libanjuta/gbf-project.h>
+#include "gbf-tree-data.h"
+
+#define GBF_TYPE_PROJECT_MODEL (gbf_project_model_get_type ())
+#define GBF_PROJECT_MODEL(obj) (GTK_CHECK_CAST ((obj), GBF_TYPE_PROJECT_MODEL, GbfProjectModel))
+#define GBF_PROJECT_MODEL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GBF_TYPE_PROJECT_MODEL, GbfProjectModelClass))
+#define GBF_IS_PROJECT_MODEL(obj) (GTK_CHECK_TYPE ((obj), GBF_TYPE_PROJECT_MODEL))
+#define GBF_IS_PROJECT_MODEL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GBF_TYPE_PROJECT_MODEL))
+
+typedef struct _GbfProjectModel GbfProjectModel;
+typedef struct _GbfProjectModelClass GbfProjectModelClass;
+typedef struct _GbfProjectModelPrivate GbfProjectModelPrivate;
+
+enum {
+ GBF_PROJECT_MODEL_COLUMN_DATA,
+ GBF_PROJECT_MODEL_NUM_COLUMNS
+};
+
+struct _GbfProjectModel {
+ GtkTreeStore parent;
+ GbfProjectModelPrivate *priv;
+};
+
+struct _GbfProjectModelClass {
+ GtkTreeStoreClass parent_class;
+};
+
+GType gbf_project_model_get_type (void);
+GbfProjectModel *gbf_project_model_new (GbfProject *project);
+
+void gbf_project_model_set_project (GbfProjectModel *model,
+ GbfProject *project);
+GbfProject *gbf_project_model_get_project (GbfProjectModel *model);
+
+GtkTreePath *gbf_project_model_get_project_root (GbfProjectModel *model);
+gboolean gbf_project_model_find_id (GbfProjectModel *model,
+ GtkTreeIter *iter,
+ GbfTreeNodeType type,
+ const gchar *id);
+
+#endif
Added: trunk/plugins/project-manager/gbf-project-util.c
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-util.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,822 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
+ *
+ * Copyright (C) 2003 Gustavo GirÃldez
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Gustavo GirÃldez <gustavo giraldez gmx net>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdarg.h>
+#include <glade/glade-xml.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libgnomevfs/gnome-vfs-uri.h>
+#include <libgnomevfs/gnome-vfs-utils.h>
+#include <bonobo/bonobo-file-selector-util.h>
+
+#include "gbf-tree-data.h"
+#include "gbf-project-view.h"
+#include "gbf-project-model.h"
+#include "gbf-project-util.h"
+
+#define ICON_SIZE 16
+
+#define GLADE_FILE PACKAGE_DATA_DIR "/glade/create_dialogs.glade"
+
+enum {
+ COLUMN_FILE,
+ COLUMN_URI,
+ N_COLUMNS
+};
+
+static GtkWidget *
+custom_widget_handler (GladeXML *xml,
+ gchar *func_name,
+ gchar *name,
+ gchar *string1,
+ gchar *string2,
+ gint int1,
+ gint int2,
+ gpointer user_data)
+{
+ GtkWidget *wid = NULL;
+
+ if (!strcmp (string1, "GtkComboBox")) {
+ wid = gtk_combo_box_new ();
+ } else if (!strcmp (string1, "GbfProjectView")) {
+ wid = gbf_project_view_new ();
+ } else {
+ g_warning ("Unknown custom widget type '%s'", string1);
+ }
+
+ return wid;
+}
+
+static GladeXML *
+load_interface (const gchar *top_widget)
+{
+ GladeXML *xml;
+
+ glade_set_custom_handler (custom_widget_handler, NULL);
+ xml = glade_xml_new (GLADE_FILE, top_widget, GETTEXT_PACKAGE);
+
+ if (!xml) {
+ g_warning ("%s", _("Couldn't load glade file"));
+ return NULL;
+ }
+
+ return xml;
+}
+
+static gboolean
+groups_filter_fn (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
+{
+ GbfTreeData *data = NULL;
+ gboolean retval = FALSE;
+
+ gtk_tree_model_get (model, iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data, -1);
+ retval = (data && data->type == GBF_TREE_NODE_GROUP);
+ gbf_tree_data_free (data);
+
+ return retval;
+}
+
+static void
+setup_groups_treeview (GbfProjectModel *model,
+ GtkWidget *view,
+ const gchar *select_group)
+{
+ GtkTreeModel *filter;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ g_return_if_fail (model != NULL);
+ g_return_if_fail (view != NULL && GBF_IS_PROJECT_VIEW (view));
+
+ filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (model), NULL);
+ gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
+ groups_filter_fn, NULL, NULL);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (view), GTK_TREE_MODEL (filter));
+ g_object_unref (filter);
+
+ /* select default group */
+ if (select_group && gbf_project_model_find_id (model, &iter,
+ GBF_TREE_NODE_GROUP, select_group)) {
+ GtkTreeIter iter_filter;
+
+ gtk_tree_model_filter_convert_child_iter_to_iter (
+ GTK_TREE_MODEL_FILTER (filter), &iter_filter, &iter);
+ path = gtk_tree_model_get_path (filter, &iter_filter);
+ gtk_tree_view_expand_to_path (GTK_TREE_VIEW (view), path);
+ } else {
+ GtkTreePath *root_path;
+
+ gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
+ root_path = gbf_project_model_get_project_root (model);
+ if (root_path) {
+ path = gtk_tree_model_filter_convert_child_path_to_path (
+ GTK_TREE_MODEL_FILTER (filter), root_path);
+ gtk_tree_path_free (root_path);
+ } else {
+ path = gtk_tree_path_new_first ();
+ }
+ }
+
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, NULL, FALSE);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), path, NULL,
+ TRUE, 0.5, 0.0);
+ gtk_tree_path_free (path);
+}
+
+static void
+error_dialog (GtkWindow *parent, const gchar *summary, const gchar *msg, ...)
+{
+ va_list ap;
+ gchar *tmp;
+ gchar *message;
+ GtkWidget *dialog;
+
+ va_start (ap, msg);
+ tmp = g_strdup_vprintf (msg, ap);
+ va_end (ap);
+
+ message = g_markup_printf_escaped ("<b>%s</b>\n\n%s", summary, tmp);
+ dialog = gtk_message_dialog_new_with_markup (parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s", message);
+ g_free (tmp);
+ g_free (message);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+}
+
+static void
+entry_changed_cb (GtkEditable *editable, gpointer user_data)
+{
+ GtkWidget *button = user_data;
+ gchar *text;
+
+ if (!button)
+ return;
+
+ text = gtk_editable_get_chars (editable, 0, -1);
+ if (strlen (text) > 0) {
+ gtk_widget_set_sensitive (button, TRUE);
+ gtk_widget_grab_default (button);
+ } else {
+ gtk_widget_set_sensitive (button, FALSE);
+ }
+ g_free (text);
+}
+
+gchar*
+gbf_project_util_new_group (GbfProjectModel *model,
+ GtkWindow *parent,
+ const gchar *default_group,
+ const gchar *default_group_name_to_add)
+{
+ GladeXML *gui;
+ GtkWidget *dialog, *group_name_entry, *ok_button;
+ GtkWidget *groups_view;
+ gint response;
+ GbfProject *project;
+ gboolean finished = FALSE;
+ gchar *new_group = NULL;
+
+ g_return_val_if_fail (model != NULL, NULL);
+
+ project = gbf_project_model_get_project (model);
+ if (!project)
+ return NULL;
+
+ gui = load_interface ("new_group_dialog");
+ g_return_val_if_fail (gui != NULL, NULL);
+
+ /* get all needed widgets */
+ dialog = glade_xml_get_widget (gui, "new_group_dialog");
+ groups_view = glade_xml_get_widget (gui, "groups_view");
+ group_name_entry = glade_xml_get_widget (gui, "group_name_entry");
+ ok_button = glade_xml_get_widget (gui, "ok_button");
+
+ /* set up dialog */
+ if (default_group_name_to_add)
+ gtk_entry_set_text (GTK_ENTRY (group_name_entry),
+ default_group_name_to_add);
+ g_signal_connect (group_name_entry, "changed",
+ (GCallback) entry_changed_cb, ok_button);
+ if (default_group_name_to_add)
+ gtk_widget_set_sensitive (ok_button, TRUE);
+ else
+ gtk_widget_set_sensitive (ok_button, FALSE);
+
+ setup_groups_treeview (model, groups_view, default_group);
+ gtk_widget_show (groups_view);
+
+ if (parent) {
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+ }
+
+ /* execute dialog */
+ while (!finished) {
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ {
+ GError *err = NULL;
+ GbfTreeData *data;
+ gchar *parent_id = NULL, *name;
+
+ name = gtk_editable_get_chars (
+ GTK_EDITABLE (group_name_entry), 0, -1);
+ data = gbf_project_view_find_selected (GBF_PROJECT_VIEW (groups_view),
+ GBF_TREE_NODE_GROUP);
+ if (data) {
+ gchar *new_group = NULL;
+
+ parent_id = g_strdup (data->id);
+ gbf_tree_data_free (data);
+
+ new_group = gbf_project_add_group (project, parent_id, name, &err);
+ if (err) {
+ error_dialog (parent, _("Can not add group"), "%s",
+ err->message);
+ g_error_free (err);
+ } else {
+ finished = TRUE;
+ }
+ g_free (parent_id);
+ } else {
+ error_dialog (parent, _("Can not add group"),
+ "%s", _("No parent group selected"));
+ }
+ g_free (name);
+ break;
+ }
+ default:
+ finished = TRUE;
+ break;
+ }
+ }
+
+ /* destroy stuff */
+ gtk_widget_destroy (dialog);
+ g_object_unref (gui);
+ return new_group;
+}
+
+enum {
+ TARGET_TYPE_TYPE = 0,
+ TARGET_TYPE_NAME,
+ TARGET_TYPE_PIXBUF,
+ TARGET_TYPE_N_COLUMNS
+};
+
+/* create a tree model with the target types */
+static GtkListStore *
+build_types_store (GbfProject *project)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gchar **types;
+ gint i;
+
+ types = gbf_project_get_types (project);
+ store = gtk_list_store_new (TARGET_TYPE_N_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ GDK_TYPE_PIXBUF);
+
+ for (i = 0; types [i]; i++) {
+ GdkPixbuf *pixbuf;
+ const gchar *name;
+
+ name = gbf_project_name_for_type (project, types [i]);
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(),
+ GTK_STOCK_CONVERT,
+ ICON_SIZE,
+ GTK_ICON_LOOKUP_GENERIC_FALLBACK,
+ NULL);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ TARGET_TYPE_TYPE, types [i],
+ TARGET_TYPE_NAME, name,
+ TARGET_TYPE_PIXBUF, pixbuf,
+ -1);
+
+ if (pixbuf)
+ g_object_unref (pixbuf);
+ }
+
+ g_strfreev (types);
+
+ return store;
+}
+
+gchar*
+gbf_project_util_new_target (GbfProjectModel *model,
+ GtkWindow *parent,
+ const gchar *default_group,
+ const gchar *default_target_name_to_add)
+{
+ GladeXML *gui;
+ GtkWidget *dialog, *target_name_entry, *ok_button;
+ GtkWidget *target_type_combo, *groups_view;
+ GtkListStore *types_store;
+ GtkCellRenderer *renderer;
+ gint response;
+ GbfProject *project;
+ gboolean finished = FALSE;
+ gchar *new_target = NULL;
+
+ g_return_val_if_fail (model != NULL, NULL);
+
+ project = gbf_project_model_get_project (model);
+ if (!project)
+ return NULL;
+
+ gui = load_interface ("new_target_dialog");
+ g_return_val_if_fail (gui != NULL, NULL);
+
+ /* get all needed widgets */
+ dialog = glade_xml_get_widget (gui, "new_target_dialog");
+ groups_view = glade_xml_get_widget (gui, "groups_view");
+ target_name_entry = glade_xml_get_widget (gui, "target_name_entry");
+ target_type_combo = glade_xml_get_widget (gui, "target_type_combo");
+ ok_button = glade_xml_get_widget (gui, "ok_button");
+
+ /* set up dialog */
+ if (default_target_name_to_add)
+ gtk_entry_set_text (GTK_ENTRY (target_name_entry),
+ default_target_name_to_add);
+ g_signal_connect (target_name_entry, "changed",
+ (GCallback) entry_changed_cb, ok_button);
+ if (default_target_name_to_add)
+ gtk_widget_set_sensitive (ok_button, TRUE);
+ else
+ gtk_widget_set_sensitive (ok_button, FALSE);
+
+ setup_groups_treeview (model, groups_view, default_group);
+ gtk_widget_show (groups_view);
+
+ /* setup target types combo box */
+ types_store = build_types_store (project);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (target_type_combo),
+ GTK_TREE_MODEL (types_store));
+
+ /* create cell renderers */
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (target_type_combo),
+ renderer, FALSE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (target_type_combo),
+ renderer,
+ "pixbuf", TARGET_TYPE_PIXBUF,
+ NULL);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (target_type_combo),
+ renderer, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (target_type_combo),
+ renderer,
+ "text", TARGET_TYPE_NAME,
+ NULL);
+ gtk_widget_show (target_type_combo);
+
+ /* preselect */
+ gtk_combo_box_set_active (GTK_COMBO_BOX (target_type_combo), 0);
+
+ if (parent) {
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+ }
+
+ /* execute dialog */
+ while (!finished) {
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ {
+ GError *err = NULL;
+ GbfTreeData *data;
+ GtkTreeIter iter;
+ gchar *group_id = NULL, *name, *type = NULL;
+
+ name = gtk_editable_get_chars (
+ GTK_EDITABLE (target_name_entry), 0, -1);
+ data = gbf_project_view_find_selected (GBF_PROJECT_VIEW (groups_view),
+ GBF_TREE_NODE_GROUP);
+
+ /* retrieve target type */
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (target_type_combo), &iter)) {
+ gtk_tree_model_get (GTK_TREE_MODEL (types_store), &iter,
+ TARGET_TYPE_TYPE, &type,
+ -1);
+ }
+
+ if (data && type) {
+ group_id = g_strdup (data->id);
+ gbf_tree_data_free (data);
+
+ new_target = gbf_project_add_target (project, group_id, name, type, &err);
+ if (err) {
+ error_dialog (parent, _("Can not add target"), "%s",
+ err->message);
+ g_error_free (err);
+ } else {
+ finished = TRUE;
+ }
+ g_free (group_id);
+ g_free (type);
+ } else {
+ error_dialog (parent, _("Can not add target"), "%s",
+ _("No group selected"));
+ }
+
+ g_free (name);
+
+ break;
+ }
+ default:
+ finished = TRUE;
+ break;
+ }
+ }
+
+ /* destroy stuff */
+ g_object_unref (types_store);
+ gtk_widget_destroy (dialog);
+ g_object_unref (gui);
+ return new_target;
+}
+
+static gboolean
+targets_filter_fn (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
+{
+ GbfTreeData *data = NULL;
+ gboolean retval = FALSE;
+
+ gtk_tree_model_get (model, iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data, -1);
+ retval = (data && !data->is_shortcut &&
+ (data->type == GBF_TREE_NODE_GROUP ||
+ data->type == GBF_TREE_NODE_TARGET));
+ gbf_tree_data_free (data);
+
+ return retval;
+}
+
+static void
+setup_targets_treeview (GbfProjectModel *model,
+ GtkWidget *view,
+ const gchar *select_target,
+ const gchar *select_group)
+{
+ GtkTreeModel *filter;
+ GtkTreeIter iter, iter_filter;
+ GtkTreePath *path = NULL;
+
+ g_return_if_fail (model != NULL);
+ g_return_if_fail (view != NULL && GBF_IS_PROJECT_VIEW (view));
+
+ filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (model), NULL);
+ gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
+ targets_filter_fn, NULL, NULL);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (view), GTK_TREE_MODEL (filter));
+ g_object_unref (filter);
+
+ /* select default target */
+ if (select_target) {
+ if (gbf_project_model_find_id (model, &iter,
+ GBF_TREE_NODE_TARGET, select_target)) {
+ gtk_tree_model_filter_convert_child_iter_to_iter (
+ GTK_TREE_MODEL_FILTER (filter), &iter_filter, &iter);
+ path = gtk_tree_model_get_path (filter, &iter_filter);
+ }
+ } else if (select_group) {
+ if (gbf_project_model_find_id (model, &iter,
+ GBF_TREE_NODE_GROUP, select_group)) {
+ gtk_tree_model_filter_convert_child_iter_to_iter (
+ GTK_TREE_MODEL_FILTER (filter), &iter_filter, &iter);
+ path = gtk_tree_model_get_path (filter, &iter_filter);
+ }
+ }
+ if (path)
+ {
+ gtk_tree_view_expand_to_path (GTK_TREE_VIEW (view), path);
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, NULL, FALSE);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), path, NULL,
+ TRUE, 0.5, 0.0);
+ gtk_tree_path_free (path);
+ } else {
+ gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
+ }
+}
+
+static void
+browse_button_clicked_cb (GtkWidget *widget, gpointer user_data)
+{
+ GtkTreeView *tree = user_data;
+ gchar *file, *uri;
+ GnomeVFSURI *tmp_uri;
+ GtkFileChooserDialog* dialog;
+ GtkTreeModel* model;
+ GtkTreeIter iter;
+ gint result;
+
+ g_return_if_fail (user_data != NULL && GTK_IS_TREE_VIEW (user_data));
+
+ model = gtk_tree_view_get_model(tree);
+ if (gtk_tree_model_get_iter_first(model, &iter))
+ {
+ gtk_tree_model_get(model, &iter, COLUMN_URI, &file, -1);
+ uri = g_strdup(file);
+ }
+ else
+ uri = g_strdup("");
+
+ dialog = GTK_FILE_CHOOSER_DIALOG(gtk_file_chooser_dialog_new (_("Select sources..."),
+ GTK_WINDOW (gtk_widget_get_toplevel (widget)),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ NULL));
+ tmp_uri = gnome_vfs_uri_new (uri);
+ g_free (uri);
+ uri = NULL;
+ if (tmp_uri) {
+ uri = gnome_vfs_uri_extract_dirname (tmp_uri);
+ gnome_vfs_uri_unref (tmp_uri);
+ }
+
+ gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(dialog),
+ uri ? uri : g_object_get_data (G_OBJECT (widget), "root"));
+ gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER(dialog), TRUE);
+ g_free (uri);
+
+ result = gtk_dialog_run (GTK_DIALOG (dialog));
+ switch (result)
+ {
+ case GTK_RESPONSE_ACCEPT:
+ {
+ GSList* uris = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER(dialog));
+ GSList* node_uri = uris;
+
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+
+ while (node_uri != NULL)
+ {
+ GtkTreeIter iter;
+ gchar* uri = node_uri->data;
+ gchar* file = g_path_get_basename (uri);
+ gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_FILE,
+ file, COLUMN_URI, uri, -1);
+ node_uri = g_slist_next (node_uri);
+ }
+ g_slist_free (uris);
+ break;
+ }
+ default:
+ break;
+ }
+ gtk_widget_destroy (GTK_WIDGET(dialog));
+}
+
+static void
+on_row_changed(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data)
+{
+ GtkWidget* button = GTK_WIDGET(data);
+ if (gtk_list_store_iter_is_valid(GTK_LIST_STORE(model), iter))
+ gtk_widget_set_sensitive(button, TRUE);
+ else
+ gtk_widget_set_sensitive(button, FALSE);
+}
+
+gchar*
+gbf_project_util_add_source (GbfProjectModel *model,
+ GtkWindow *parent,
+ const gchar *default_target,
+ const gchar *default_group,
+ const gchar *default_uri)
+{
+ GList* new_sources;
+ gchar* uri = NULL;
+ GList* uris = NULL;
+
+ if (default_uri) {
+ uri = g_strdup (default_uri);
+ uris = g_list_append (NULL, uri);
+ }
+ new_sources =
+ gbf_project_util_add_source_multi (model, parent,
+ default_target,
+ default_group, uris);
+ g_free (uri);
+
+ if (new_sources && g_list_length (new_sources))
+ {
+ gchar* new_source = g_strdup (new_sources->data);
+ g_list_foreach (new_sources, (GFunc) g_free, NULL);
+ g_list_free (new_sources);
+ return new_source;
+ }
+ else
+ return NULL;
+}
+
+GList*
+gbf_project_util_add_source_multi (GbfProjectModel *model,
+ GtkWindow *parent,
+ const gchar *default_target,
+ const gchar *default_group,
+ GList *uris_to_add)
+{
+ GladeXML *gui;
+ GtkWidget *dialog, *source_file_tree;
+ GtkWidget *ok_button, *browse_button;
+ GtkWidget *targets_view;
+ gint response;
+ GbfProject *project;
+ gboolean finished = FALSE;
+ gchar *project_root;
+ GtkListStore* list;
+ GtkCellRenderer* renderer;
+ GtkTreeViewColumn* column_filename;
+ GList* new_sources = NULL;
+ GList* uri_node;
+
+ g_return_val_if_fail (model != NULL, NULL);
+
+ project = gbf_project_model_get_project (model);
+ if (!project)
+ return NULL;
+
+ gui = load_interface ("add_source_dialog");
+ g_return_val_if_fail (gui != NULL, NULL);
+
+ /* get all needed widgets */
+ dialog = glade_xml_get_widget (gui, "add_source_dialog");
+ targets_view = glade_xml_get_widget (gui, "targets_view");
+ source_file_tree = glade_xml_get_widget (gui, "source_file_tree");
+ browse_button = glade_xml_get_widget (gui, "browse_button");
+ ok_button = glade_xml_get_widget (gui, "ok_button");
+
+ /* Prepare file tree */
+ list = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (source_file_tree),
+ GTK_TREE_MODEL (list));
+ renderer = gtk_cell_renderer_text_new ();
+ column_filename = gtk_tree_view_column_new_with_attributes ("Files",
+ renderer,
+ "text",
+ COLUMN_FILE,
+ NULL);
+ gtk_tree_view_column_set_sizing (column_filename,
+ GTK_TREE_VIEW_COLUMN_FIXED);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (source_file_tree),
+ column_filename);
+
+ /* set up dialog */
+ uri_node = uris_to_add;
+ while (uri_node)
+ {
+ GtkTreeIter iter;
+ gchar* filename = g_path_get_basename (uri_node->data);
+ if (!filename)
+ filename = g_strdup (uri_node->data);
+ gtk_list_store_append (list, &iter);
+ gtk_list_store_set (list, &iter, COLUMN_FILE, filename,
+ COLUMN_URI, g_strdup(uri_node->data), -1);
+ g_free (filename);
+ uri_node = g_list_next (uri_node);
+ }
+ if (!g_list_length (uris_to_add))
+ gtk_widget_set_sensitive (ok_button, FALSE);
+ else
+ gtk_widget_set_sensitive (ok_button, TRUE);
+
+ g_signal_connect (G_OBJECT(list), "row_changed",
+ G_CALLBACK(on_row_changed), ok_button);
+
+ g_signal_connect (browse_button, "clicked",
+ G_CALLBACK (browse_button_clicked_cb), source_file_tree);
+
+ g_object_get (project, "project-dir", &project_root, NULL);
+ g_object_set_data_full (G_OBJECT (browse_button), "root",
+ project_root, g_free);
+
+ setup_targets_treeview (model, targets_view, default_target,
+ default_group);
+ gtk_widget_show (targets_view);
+
+ if (parent) {
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+ }
+
+ if (default_target)
+ gtk_widget_grab_focus (source_file_tree);
+ else
+ gtk_widget_grab_focus (targets_view);
+
+ /* execute dialog */
+ while (!finished) {
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ {
+ GbfTreeData *data;
+ gchar *target_id = NULL;
+
+ data = gbf_project_view_find_selected (GBF_PROJECT_VIEW (targets_view),
+ GBF_TREE_NODE_TARGET);
+ if (data) {
+ GtkTreeIter iter;
+ GString *err_mesg = g_string_new (NULL);
+
+ target_id = g_strdup (data->id);
+ gbf_tree_data_free (data);
+
+ if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list),
+ &iter))
+ break;
+ do
+ {
+ GError *err = NULL;
+ gchar* new_source;
+ gchar* source_file;
+ gtk_tree_model_get (GTK_TREE_MODEL(list), &iter,
+ COLUMN_URI, &source_file, -1);
+
+ new_source = gbf_project_add_source (project,
+ target_id,
+ source_file,
+ &err);
+ if (err) {
+ gchar *str = g_strdup_printf ("%s: %s\n",
+ source_file,
+ err->message);
+ g_string_append (err_mesg, str);
+ g_error_free (err);
+ g_free (str);
+ }
+ else
+ new_sources = g_list_append (new_sources,
+ new_source);
+
+ g_free (source_file);
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL(list),
+ &iter));
+
+ g_free (target_id);
+
+ if (err_mesg->str && strlen (err_mesg->str) > 0) {
+ error_dialog (parent, _("Can not add source files"),
+ "%s", err_mesg->str);
+ } else {
+ finished = TRUE;
+ }
+ g_string_free (err_mesg, TRUE);
+ } else {
+ error_dialog (parent, _("Can not add source files"),
+ "%s", _("No target has been selected"));
+ }
+
+ break;
+ }
+ default:
+ gtk_list_store_clear (GTK_LIST_STORE (list));
+ finished = TRUE;
+ break;
+ }
+ }
+
+ /* destroy stuff */
+ gtk_widget_destroy (dialog);
+ g_object_unref (gui);
+ return new_sources;
+}
Added: trunk/plugins/project-manager/gbf-project-util.h
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-util.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
+ *
+ * Copyright (C) 2003 Gustavo GirÃldez
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Gustavo GirÃldez <gustavo giraldez gmx net>
+ */
+
+#ifndef __GBF_PROJECT_UTIL_H__
+#define __GBF_PROJECT_UTIL_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "gbf-project-model.h"
+
+G_BEGIN_DECLS
+
+gchar* gbf_project_util_new_group (GbfProjectModel *model,
+ GtkWindow *parent,
+ const gchar *default_group,
+ const gchar *default_group_name_to_add);
+
+gchar* gbf_project_util_new_target (GbfProjectModel *model,
+ GtkWindow *parent,
+ const gchar *default_group,
+ const gchar *default_target_name_to_add);
+
+gchar* gbf_project_util_add_source (GbfProjectModel *model,
+ GtkWindow *parent,
+ const gchar *default_target,
+ const gchar *default_group,
+ const gchar *default_uri_to_add);
+
+GList* gbf_project_util_add_source_multi (GbfProjectModel *model,
+ GtkWindow *parent,
+ const gchar *default_target,
+ const gchar *default_group,
+ GList *uris_to_add);
+
+
+
+G_END_DECLS
+
+#endif /* __GBF_PROJECT_UTIL_H__ */
Added: trunk/plugins/project-manager/gbf-project-view.c
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-view.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,416 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8; coding: utf-8 -*- */
+/* gbf_project-tree.c
+ *
+ * Copyright (C) 2000 JP Rosevear
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: JP Rosevear
+ * Gustavo GirÃldez <gustavo giraldez gmx net>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <libgnome/gnome-macros.h>
+#include <gio/gio.h>
+#include <string.h>
+
+#include "gbf-tree-data.h"
+#include "gbf-project-model.h"
+#include "gbf-project-view.h"
+
+#define ICON_SIZE 16
+
+struct _GbfProjectViewPrivate {
+
+};
+
+enum {
+ URI_ACTIVATED,
+ TARGET_SELECTED,
+ GROUP_SELECTED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+static void gbf_project_view_class_init (GbfProjectViewClass *klass);
+static void gbf_project_view_instance_init (GbfProjectView *tree);
+static void destroy (GtkObject *object);
+
+static void set_pixbuf (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data);
+static void set_text (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data);
+
+
+GNOME_CLASS_BOILERPLATE(GbfProjectView, gbf_project_view,
+ GtkTreeView, GTK_TYPE_TREE_VIEW);
+
+
+static void
+row_activated (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GbfTreeData *data;
+
+ model = gtk_tree_view_get_model (tree_view);
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+ -1);
+
+ if (data->uri) {
+ g_signal_emit (G_OBJECT (tree_view),
+ signals [URI_ACTIVATED], 0,
+ data->uri);
+ }
+
+ if (data->type == GBF_TREE_NODE_TARGET) {
+ g_signal_emit (G_OBJECT (tree_view),
+ signals [TARGET_SELECTED], 0,
+ data->id);
+ }
+
+ if (data->type == GBF_TREE_NODE_GROUP) {
+ g_signal_emit (G_OBJECT (tree_view),
+ signals [GROUP_SELECTED], 0,
+ data->id);
+ }
+
+ gbf_tree_data_free (data);
+}
+
+static void
+destroy (GtkObject *object)
+{
+ GbfProjectView *tree;
+ GbfProjectViewPrivate *priv;
+
+ tree = GBF_PROJECT_VIEW (object);
+ priv = tree->priv;
+
+ if (priv) {
+ g_free (priv);
+ tree->priv = NULL;
+ }
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static GdkPixbuf*
+get_icon (const gchar* uri)
+{
+ const gchar** icon_names;
+ GtkIconInfo* icon_info;
+ GIcon* icon;
+ GdkPixbuf* pixbuf = NULL;
+ GFile* file;
+ GFileInfo* file_info;
+ GError *error = NULL;
+
+ file = g_file_new_for_uri (uri);
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_ICON,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ if (!file_info)
+ {
+ g_warning (G_STRLOC ": Unable to query information for URI: %s: %s", uri, error->message);
+ g_clear_error (&error);
+ return NULL;
+ }
+
+ icon = g_file_info_get_icon(file_info);
+ g_object_get (icon, "names", &icon_names, NULL);
+ icon_info = gtk_icon_theme_choose_icon (gtk_icon_theme_get_default(),
+ icon_names,
+ ICON_SIZE,
+ GTK_ICON_LOOKUP_GENERIC_FALLBACK);
+ pixbuf = gtk_icon_info_load_icon (icon_info, NULL);
+ gtk_icon_info_free(icon_info);
+
+ return pixbuf;
+}
+
+static void
+set_pixbuf (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ //GbfProjectView *view = GBF_PROJECT_VIEW (user_data);
+ GbfTreeData *data = NULL;
+ GdkPixbuf *pixbuf = NULL;
+
+ gtk_tree_model_get (model, iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data, -1);
+ g_return_if_fail (data != NULL);
+
+ switch (data->type) {
+ case GBF_TREE_NODE_TARGET_SOURCE:
+ {
+ pixbuf = get_icon (data->uri);
+ break;
+ }
+ case GBF_TREE_NODE_GROUP:
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(),
+ GTK_STOCK_DIRECTORY,
+ ICON_SIZE,
+ GTK_ICON_LOOKUP_GENERIC_FALLBACK,
+ NULL);
+ break;
+ case GBF_TREE_NODE_TARGET:
+ {
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(),
+ GTK_STOCK_CONVERT,
+ ICON_SIZE,
+ GTK_ICON_LOOKUP_GENERIC_FALLBACK,
+ NULL);
+ break;
+ }
+ default:
+ pixbuf = NULL;
+ }
+
+ g_object_set (GTK_CELL_RENDERER (cell), "pixbuf", pixbuf, NULL);
+ if (pixbuf)
+ g_object_unref (pixbuf);
+
+ gbf_tree_data_free (data);
+}
+
+static void
+set_text (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ GbfTreeData *data;
+
+ gtk_tree_model_get (model, iter, 0, &data, -1);
+ g_object_set (GTK_CELL_RENDERER (cell), "text",
+ data->name, NULL);
+ gbf_tree_data_free (data);
+}
+
+static gboolean
+search_equal_func (GtkTreeModel *model, gint column,
+ const gchar *key, GtkTreeIter *iter,
+ gpointer user_data)
+{
+ GbfTreeData *data;
+ gboolean ret = TRUE;
+
+ gtk_tree_model_get (model, iter, 0, &data, -1);
+ if (strncmp (data->name, key, strlen (key)) == 0)
+ ret = FALSE;
+ gbf_tree_data_free (data);
+ return ret;
+}
+
+static gint
+expose_event (GtkWidget *widget, GdkEventExpose *ev)
+{
+ GtkTreeModel *model;
+ GtkTreeView *tree_view;
+ gint event_handled = FALSE;
+
+ event_handled = GNOME_CALL_PARENT_WITH_DEFAULT (
+ GTK_WIDGET_CLASS, expose_event, (widget, ev), event_handled);
+
+ tree_view = GTK_TREE_VIEW (widget);
+ model = gtk_tree_view_get_model (tree_view);
+ if (ev->window == gtk_tree_view_get_bin_window (tree_view) &&
+ model && GBF_IS_PROJECT_MODEL (model)) {
+ GtkTreePath *root;
+ GdkRectangle rect;
+
+ /* paint an horizontal ruler to separate the project
+ * tree from the target shortcuts */
+
+ root = gbf_project_model_get_project_root (GBF_PROJECT_MODEL (model));
+ if (root) {
+ gtk_tree_view_get_background_area (
+ tree_view, root, gtk_tree_view_get_column (tree_view, 0), &rect);
+ gtk_paint_hline (widget->style,
+ ev->window,
+ GTK_WIDGET_STATE (widget),
+ &ev->area,
+ widget,
+ "",
+ rect.x, rect.x + rect.width,
+ rect.y);
+ gtk_tree_path_free (root);
+ }
+ }
+
+ return event_handled;
+}
+
+static void
+gbf_project_view_class_init (GbfProjectViewClass *klass)
+{
+ GObjectClass *g_object_class;
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkTreeViewClass *tree_view_class;
+
+ g_object_class = G_OBJECT_CLASS (klass);
+ object_class = (GtkObjectClass *) klass;
+ widget_class = GTK_WIDGET_CLASS (klass);
+ tree_view_class = GTK_TREE_VIEW_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->destroy = destroy;
+ widget_class->expose_event = expose_event;
+ tree_view_class->row_activated = row_activated;
+
+ signals [URI_ACTIVATED] =
+ g_signal_new ("uri_activated",
+ GBF_TYPE_PROJECT_VIEW,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GbfProjectViewClass,
+ uri_activated),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ signals [TARGET_SELECTED] =
+ g_signal_new ("target_selected",
+ GBF_TYPE_PROJECT_VIEW,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GbfProjectViewClass,
+ target_selected),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+ signals [GROUP_SELECTED] =
+ g_signal_new ("group_selected",
+ GBF_TYPE_PROJECT_VIEW,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GbfProjectViewClass,
+ group_selected),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void
+gbf_project_view_instance_init (GbfProjectView *tree)
+{
+ GbfProjectViewPrivate *priv;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ static GtkTargetEntry row_targets[] = {
+ { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, 0 }
+ };
+
+ priv = g_new0 (GbfProjectViewPrivate, 1);
+ tree->priv = priv;
+
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree), FALSE);
+ gtk_tree_view_set_enable_search (GTK_TREE_VIEW (tree), TRUE);
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (tree), 0);
+ gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (tree),
+ search_equal_func,
+ NULL, NULL);
+
+ gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (tree),
+ GDK_BUTTON1_MASK,
+ row_targets,
+ G_N_ELEMENTS (row_targets),
+ GDK_ACTION_MOVE);
+ gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (tree),
+ row_targets,
+ G_N_ELEMENTS (row_targets),
+ GDK_ACTION_MOVE);
+
+ /* set renderer for files column. */
+ column = gtk_tree_view_column_new ();
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+ gtk_tree_view_column_set_cell_data_func (column, renderer, set_pixbuf, tree, NULL);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+ gtk_tree_view_column_set_cell_data_func (column, renderer, set_text, tree, NULL);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
+}
+
+GtkWidget *
+gbf_project_view_new (void)
+{
+ return GTK_WIDGET (g_object_new (GBF_TYPE_PROJECT_VIEW, NULL));
+}
+
+GbfTreeData *
+gbf_project_view_find_selected (GbfProjectView *view, GbfTreeNodeType type)
+{
+ GbfTreeData *data = NULL;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter, iter2;
+
+ g_return_val_if_fail (view != NULL, NULL);
+ g_return_val_if_fail (GBF_IS_PROJECT_VIEW (view), NULL);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_tree_model_get (model, &iter,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+ -1);
+ /* walk up the hierarchy searching for a node of the given type */
+ while (NULL != data && data->type != type) {
+ gbf_tree_data_free (data);
+ data = NULL;
+
+ if (!gtk_tree_model_iter_parent (model, &iter2, &iter))
+ break;
+
+ gtk_tree_model_get (model, &iter2,
+ GBF_PROJECT_MODEL_COLUMN_DATA, &data,
+ -1);
+ iter = iter2;
+ }
+ }
+
+ return data;
+}
+
Added: trunk/plugins/project-manager/gbf-project-view.h
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-project-view.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,68 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-project-view.h
+ *
+ * Copyright (C) 2000-2002 JP Rosevear
+ * Copyright (C) 2002 Dave Camp
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _GBF_PROJECT_TREE_H_
+#define _GBF_PROJECT_TREE_H_
+
+#include <gtk/gtk.h>
+#include "gbf-tree-data.h"
+
+G_BEGIN_DECLS
+
+#define GBF_TYPE_PROJECT_VIEW (gbf_project_view_get_type ())
+#define GBF_PROJECT_VIEW(obj) (GTK_CHECK_CAST ((obj), GBF_TYPE_PROJECT_VIEW, GbfProjectView))
+#define GBF_PROJECT_VIEW_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GBF_TYPE_PROJECT_VIEW, GbfProjectViewClass))
+#define GBF_IS_PROJECT_VIEW(obj) (GTK_CHECK_TYPE ((obj), GBF_TYPE_PROJECT_VIEW))
+#define GBF_IS_PROJECT_VIEW_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((obj), GBF_TYPE_PROJECT_VIEW))
+
+typedef struct _GbfProjectView GbfProjectView;
+typedef struct _GbfProjectViewPrivate GbfProjectViewPrivate;
+typedef struct _GbfProjectViewClass GbfProjectViewClass;
+
+
+struct _GbfProjectView {
+ GtkTreeView parent;
+
+ GbfProjectViewPrivate *priv;
+};
+
+struct _GbfProjectViewClass {
+ GtkTreeViewClass parent_class;
+
+ void (* uri_activated) (GbfProjectView *project_view,
+ const char *uri);
+
+ void (* target_selected) (GbfProjectView *project_view,
+ const gchar *target_id);
+ void (* group_selected) (GbfProjectView *project_view,
+ const gchar *group_id);
+};
+
+GType gbf_project_view_get_type (void);
+GtkWidget *gbf_project_view_new (void);
+
+GbfTreeData *gbf_project_view_find_selected (GbfProjectView *view,
+ GbfTreeNodeType type);
+
+G_END_DECLS
+
+#endif /* _GBF_PROJECT_VIEW_H_ */
Added: trunk/plugins/project-manager/gbf-tree-data.c
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-tree-data.c Wed Dec 31 11:55:52 2008
@@ -0,0 +1,123 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-tree-data.c
+ *
+ * Copyright (C) 2000 JP Rosevear
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: JP Rosevear
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libgnomevfs/gnome-vfs-uri.h>
+#include "gbf-tree-data.h"
+
+GType
+gbf_tree_data_get_type (void)
+{
+ static GType our_type = 0;
+
+ if (our_type == 0)
+ our_type = g_boxed_type_register_static ("GbfProjectTreeNodeData",
+ (GBoxedCopyFunc) gbf_tree_data_copy,
+ (GBoxedFreeFunc) gbf_tree_data_free);
+
+ return our_type;
+}
+
+GbfTreeData *
+gbf_tree_data_new_string (const gchar *string)
+{
+ GbfTreeData *node = g_new0 (GbfTreeData, 1);
+
+ node->type = GBF_TREE_NODE_STRING;
+ node->name = g_strdup (string);
+
+ return node;
+}
+
+GbfTreeData *
+gbf_tree_data_new_group (GbfProject *project, const GbfProjectGroup *group)
+{
+ GbfTreeData *node = g_new0 (GbfTreeData, 1);
+
+ node->type = GBF_TREE_NODE_GROUP;
+ node->name = g_strdup (group->name);
+ node->id = g_strdup (group->id);
+
+ return node;
+}
+
+GbfTreeData *
+gbf_tree_data_new_target (GbfProject *project, const GbfProjectTarget *target)
+{
+ GbfTreeData *node = g_new0 (GbfTreeData, 1);
+
+ node->type = GBF_TREE_NODE_TARGET;
+ node->name = g_strdup (target->name);
+ node->id = g_strdup (target->id);
+ node->mime_type = g_strdup (gbf_project_mimetype_for_type (project, target->type));
+
+ return node;
+}
+
+GbfTreeData *
+gbf_tree_data_new_source (GbfProject *project, const GbfProjectTargetSource *source)
+{
+ GbfTreeData *node = g_new0 (GbfTreeData, 1);
+ GnomeVFSURI *uri;
+
+ node->type = GBF_TREE_NODE_TARGET_SOURCE;
+ node->id = g_strdup (source->id);
+ node->uri = g_strdup (source->source_uri);
+
+ uri = gnome_vfs_uri_new (source->source_uri);
+ node->name = gnome_vfs_uri_extract_short_name (uri);
+ gnome_vfs_uri_unref (uri);
+
+ return node;
+}
+
+GbfTreeData *
+gbf_tree_data_copy (GbfTreeData *src)
+{
+ GbfTreeData *node;
+
+ node = g_new (GbfTreeData, 1);
+ node->type = src->type;
+ node->name = g_strdup (src->name);
+ node->id = g_strdup (src->id);
+ node->uri = g_strdup (src->uri);
+ node->is_shortcut = src->is_shortcut;
+ node->mime_type = g_strdup (src->mime_type);
+
+ return node;
+}
+
+void
+gbf_tree_data_free (GbfTreeData *node)
+{
+ if (node) {
+ g_free (node->name);
+ g_free (node->id);
+ g_free (node->uri);
+ g_free (node->mime_type);
+ g_free (node);
+ }
+}
Added: trunk/plugins/project-manager/gbf-tree-data.h
==============================================================================
--- (empty file)
+++ trunk/plugins/project-manager/gbf-tree-data.h Wed Dec 31 11:55:52 2008
@@ -0,0 +1,67 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gbf-tree-data.h
+ *
+ * Copyright (C) 2000 JP Rosevear
+ *
+ * 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 2 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: JP Rosevear
+ */
+
+#ifndef _GBF_TREE_DATA_H_
+#define _GBF_TREE_DATA_H_
+
+#include <glib-object.h>
+#include <libanjuta/gbf-project.h>
+
+G_BEGIN_DECLS
+
+#define GBF_TYPE_TREE_DATA (gbf_tree_data_get_type ())
+
+typedef struct _GbfTreeData GbfTreeData;
+
+typedef enum {
+ GBF_TREE_NODE_STRING,
+ GBF_TREE_NODE_GROUP,
+ GBF_TREE_NODE_TARGET,
+ GBF_TREE_NODE_TARGET_SOURCE,
+} GbfTreeNodeType;
+
+struct _GbfTreeData
+{
+ GbfTreeNodeType type;
+ gchar *name;
+ gchar *id;
+ gchar *uri;
+ gboolean is_shortcut;
+ gchar *mime_type;
+};
+
+GType gbf_tree_data_get_type (void);
+GbfTreeData *gbf_tree_data_new_string (const gchar *string);
+GbfTreeData *gbf_tree_data_new_group (GbfProject *project,
+ const GbfProjectGroup *group);
+GbfTreeData *gbf_tree_data_new_target (GbfProject *project,
+ const GbfProjectTarget *target);
+GbfTreeData *gbf_tree_data_new_source (GbfProject *project,
+ const GbfProjectTargetSource *source);
+GbfTreeData *gbf_tree_data_copy (GbfTreeData *data);
+void gbf_tree_data_free (GbfTreeData *data);
+
+
+G_END_DECLS
+
+#endif /* _GBF_TREE_DATA_H_ */
Modified: trunk/plugins/project-manager/plugin.c
==============================================================================
--- trunk/plugins/project-manager/plugin.c (original)
+++ trunk/plugins/project-manager/plugin.c Wed Dec 31 11:55:52 2008
@@ -28,12 +28,12 @@
#include <libanjuta/interfaces/ianjuta-document-manager.h>
#include <libanjuta/interfaces/ianjuta-file-manager.h>
#include <libanjuta/interfaces/ianjuta-builder.h>
+#include <libanjuta/interfaces/ianjuta-project-backend.h>
#include <libanjuta/anjuta-profile-manager.h>
#include <libanjuta/anjuta-debug.h>
#include <libanjuta/anjuta-status.h>
-#include <gbf/gbf-project-util.h>
-#include <gbf/gbf-backend.h>
+#include "gbf-project-util.h"
#include "plugin.h"
@@ -1177,12 +1177,13 @@
static void
project_manager_load_gbf (ProjectManagerPlugin *pm_plugin)
{
+ AnjutaPluginManager *plugin_manager;
AnjutaStatus *status;
gchar *dirname;
const gchar *root_uri;
- GSList *l;
GError *error = NULL;
- GbfBackend *backend = NULL;
+ GList *descs = NULL;
+ GList *desc;
root_uri = pm_plugin->project_root_uri;
@@ -1193,12 +1194,24 @@
if (pm_plugin->project != NULL)
g_object_unref (pm_plugin->project);
- DEBUG_PRINT ("%s", "initializing gbf backend...\n");
- gbf_backend_init ();
- for (l = gbf_backend_get_backends (); l; l = l->next) {
- backend = l->data;
+ DEBUG_PRINT ("loading gbf backend...\n");
+ plugin_manager = anjuta_shell_get_plugin_manager (ANJUTA_PLUGIN(pm_plugin)->shell, NULL);
+ descs = anjuta_plugin_manager_query (plugin_manager,
+ "Anjuta Plugin",
+ "Interfaces",
+ "IAnjutaProjectBackend",
+ NULL);
+ for (desc = g_list_first (descs); desc != NULL; desc = g_list_next (desc)) {
+ AnjutaPluginDescription *backend;
+ IAnjutaProjectBackend *plugin;
+ gchar *location = NULL;
- pm_plugin->project = gbf_backend_new_project (backend->id);
+ backend = (AnjutaPluginDescription *)desc->data;
+ anjuta_plugin_description_get_string (backend, "Anjuta Plugin", "Location", &location);
+ plugin = (IAnjutaProjectBackend *)anjuta_plugin_manager_get_plugin_by_id (plugin_manager, location);
+ g_free (location);
+
+ pm_plugin->project = ianjuta_project_backend_new_project (plugin, NULL);
if (pm_plugin->project)
{
if (gbf_project_probe (pm_plugin->project, dirname, NULL))
@@ -1213,10 +1226,11 @@
if (!strcmp (backend->id, "gbf-am:GbfAmProject"))
break;
*/
- backend = NULL;
+ plugin = NULL;
}
+ g_list_free (descs);
- if (!backend)
+ if (!pm_plugin->project)
{
/* FIXME: Set err */
g_warning ("no backend available for this project\n");
@@ -1465,7 +1479,7 @@
/* GladeXML *gxml; */
ProjectManagerPlugin *pm_plugin;
- DEBUG_PRINT ("%s", "ProjectManagerPlugin: Activating Project Manager plugin...");
+ DEBUG_PRINT ("ProjectManagerPlugin: Activating Project Manager plugin %p...", plugin);
if (!initialized)
register_stock_icons (plugin);
@@ -1577,6 +1591,7 @@
ProjectManagerPlugin *pm_plugin;
pm_plugin = ANJUTA_PLUGIN_PROJECT_MANAGER (plugin);
+ DEBUG_PRINT ("ProjectManagerPlugin: Deactivate Project Manager plugin...");
if (pm_plugin->close_project_idle > -1)
{
g_source_remove (pm_plugin->close_project_idle);
@@ -1699,7 +1714,7 @@
{
gchar *path, *ptr;
gchar *uri;
- const gchar *project_root;
+ const gchar *project_root = NULL;
if (!id)
return NULL;
@@ -1721,6 +1736,15 @@
anjuta_shell_get (ANJUTA_PLUGIN (plugin)->shell,
root, G_TYPE_STRING,
&project_root, NULL);
+ if (project_root == NULL)
+ {
+ /* Perhaps missing build URI, use project URI instead */
+ anjuta_shell_get (ANJUTA_PLUGIN (plugin)->shell,
+ IANJUTA_PROJECT_MANAGER_PROJECT_ROOT_URI,
+ G_TYPE_STRING,
+ &project_root,
+ NULL);
+ }
uri = g_build_filename (project_root, path, NULL);
/* DEBUG_PRINT ("Converting id: %s to %s", id, uri); */
g_free (path);
@@ -1735,6 +1759,15 @@
anjuta_shell_get (ANJUTA_PLUGIN (plugin)->shell,
root, G_TYPE_STRING,
&project_root, NULL);
+ if (project_root == NULL)
+ {
+ /* Perhaps missing build URI, use project URI instead */
+ anjuta_shell_get (ANJUTA_PLUGIN (plugin)->shell,
+ IANJUTA_PROJECT_MANAGER_PROJECT_ROOT_URI,
+ G_TYPE_STRING,
+ &project_root,
+ NULL);
+ }
if (project_root)
{
if (uri[0] != '/')
@@ -2152,7 +2185,7 @@
{
ProjectManagerPlugin *plugin;
IAnjutaProjectManagerElementType default_location_type;
- gchar *location_id;
+ gchar *location_id = NULL;
gchar* source_id;
gchar* source_uri;
@@ -2210,7 +2243,7 @@
{
ProjectManagerPlugin *plugin;
IAnjutaProjectManagerElementType default_location_type;
- gchar *location_id;
+ gchar *location_id = NULL;
GList* source_ids;
GList* source_uris = NULL;
@@ -2392,7 +2425,7 @@
GError *error = NULL;
gchar* uri = g_file_get_uri (file);
GnomeVFSURI* vfs_uri;
-
+
plugin = ANJUTA_PLUGIN_PROJECT_MANAGER (ifile);
/* If there is already a project loaded, load in separate anjuta window */
@@ -2403,6 +2436,8 @@
g_free (quoted_uri);
anjuta_util_execute_shell (NULL, cmd);
g_free (cmd);
+ g_free (uri);
+
return;
}
@@ -2420,26 +2455,30 @@
session_profile = g_file_new_for_uri (DEFAULT_PROFILE);
anjuta_profile_add_plugins_from_xml (profile, session_profile,
TRUE, &error);
+ g_object_unref (session_profile);
if (error)
{
- anjuta_util_dialog_error (GTK_WINDOW (ANJUTA_PLUGIN (ifile)->shell),
- "%s", error->message);
- g_error_free (error);
- error = NULL;
+ g_propagate_error (e, error);
+
+ g_free(uri);
+ g_object_unref (profile);
+
+ return;
}
- g_object_unref (session_profile);
/* Project default profile */
session_profile = g_file_new_for_uri (uri);
anjuta_profile_add_plugins_from_xml (profile, session_profile, TRUE, &error);
+ g_object_unref (session_profile);
if (error)
{
- anjuta_util_dialog_error (GTK_WINDOW (ANJUTA_PLUGIN (ifile)->shell),
- "%s", error->message);
- g_error_free (error);
- error = NULL;
+ g_propagate_error (e, error);
+
+ g_free(uri);
+ g_object_unref (profile);
+
+ return;
}
- g_object_unref (session_profile);
/* Project session profile */
vfs_uri = gnome_vfs_uri_new (uri);
@@ -2461,14 +2500,16 @@
FALSE, &error);
if (error)
{
- anjuta_util_dialog_error (GTK_WINDOW (ANJUTA_PLUGIN (ifile)->shell),
- "%s", error->message);
- g_error_free (error);
- error = NULL;
+ g_propagate_error (e, error);
+
+ g_free(uri);
+ g_object_unref (profile);
+ g_object_unref (session_profile);
+
+ return;
}
}
anjuta_profile_set_sync_file (profile, session_profile);
- g_object_unref (session_profile);
g_free (session_profile_path);
g_free (profile_name);
Modified: trunk/plugins/project-manager/plugin.h
==============================================================================
--- trunk/plugins/project-manager/plugin.h (original)
+++ trunk/plugins/project-manager/plugin.h Wed Dec 31 11:55:52 2008
@@ -22,9 +22,9 @@
#define _PROJECT_MANAGER_PLUGIN_H_
#include <libanjuta/anjuta-plugin.h>
-#include <gbf/gbf-project.h>
-#include <gbf/gbf-project-model.h>
-#include <gbf/gbf-project-view.h>
+#include <libanjuta/gbf-project.h>
+#include "gbf-project-model.h"
+#include "gbf-project-view.h"
extern GType project_manager_plugin_get_type (GTypeModule *module);
#define ANJUTA_TYPE_PLUGIN_PROJECT_MANAGER (project_manager_plugin_get_type (NULL))
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]