[gnome-builder/wip/chergert/runtimes] wip: runtimes
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/runtimes] wip: runtimes
- Date: Sun, 14 Feb 2016 21:46:41 +0000 (UTC)
commit 045383f8f81b7c3ddaee4acb8b20c58d068d44ad
Author: Christian Hergert <chergert redhat com>
Date: Sun Feb 14 13:46:24 2016 -0800
wip: runtimes
.buildconfig | 36 +
Makefile.am | 8 +-
configure.ac | 3 +-
data/icons/hicolor/icons.gresource.xml | 1 +
.../actions/builder-build-configure-symbolic.svg | 26 +
data/icons/src/symbolic.svg | 31 +-
data/theme/Adwaita-shared.css | 20 +-
data/ui/ide-environment-editor-row.ui | 48 ++
data/ui/ide-omni-search-row.ui | 1 +
libide/Makefile.am | 40 +-
libide/ide-build-system.c | 63 +-
libide/ide-build-system.h | 6 +-
libide/ide-builder.c | 175 ++++-
libide/ide-builder.h | 29 +-
libide/ide-configuration-manager.c | 736 +++++++++++++++++
libide/ide-configuration-manager.h | 53 ++
libide/ide-configuration.c | 824 ++++++++++++++++++++
libide/ide-configuration.h | 79 ++
libide/ide-context.c | 185 +++++
libide/ide-context.h | 93 ++-
libide/ide-device-manager.c | 3 +
libide/ide-device.c | 33 +-
libide/ide-device.h | 23 +-
libide/ide-environment-editor-row.c | 272 +++++++
libide/ide-environment-editor-row.h | 39 +
libide/ide-environment-editor.c | 311 ++++++++
libide/ide-environment-editor.h | 39 +
libide/ide-environment-variable.c | 179 +++++
libide/ide-environment-variable.h | 41 +
libide/ide-environment.c | 265 +++++++
libide/ide-environment.h | 47 ++
libide/ide-internal.h | 1 +
libide/ide-runtime-manager.c | 239 ++++++
libide/ide-runtime-manager.h | 39 +
libide/ide-runtime-provider.c | 61 ++
libide/ide-runtime-provider.h | 49 ++
libide/ide-runtime.c | 408 ++++++++++
libide/ide-runtime.h | 93 +++
libide/ide-subprocess-launcher.c | 511 ++++++++++++
libide/ide-subprocess-launcher.h | 81 ++
libide/ide-types.h | 12 +-
libide/ide.h | 11 +
libide/local/ide-local-device.c | 20 +-
libide/resources/libide.gresource.xml | 1 +
libide/util/ide-posix.c | 37 +
libide/util/ide-posix.h | 30 +
plugins/Makefile.am | 1 +
plugins/autotools/ide-autotools-build-system.c | 147 ++--
plugins/autotools/ide-autotools-build-task.c | 571 ++++++--------
plugins/autotools/ide-autotools-build-task.h | 10 +-
plugins/autotools/ide-autotools-builder.c | 229 +-----
plugins/autotools/ide-autotools-builder.h | 7 -
plugins/build-tools/Makefile.am | 6 +
plugins/build-tools/gbp-build-configuration-row.c | 153 ++++
plugins/build-tools/gbp-build-configuration-row.h | 36 +
plugins/build-tools/gbp-build-configuration-row.ui | 33 +
plugins/build-tools/gbp-build-configuration-view.c | 417 ++++++++++
plugins/build-tools/gbp-build-configuration-view.h | 38 +
.../build-tools/gbp-build-configuration-view.ui | 268 +++++++
plugins/build-tools/gbp-build-panel.c | 177 ++---
plugins/build-tools/gbp-build-panel.ui | 180 ++---
plugins/build-tools/gbp-build-perspective.c | 428 ++++++++++
plugins/build-tools/gbp-build-perspective.h | 36 +
plugins/build-tools/gbp-build-perspective.ui | 53 ++
plugins/build-tools/gbp-build-tool.c | 50 +-
plugins/build-tools/gbp-build-tools.gresource.xml | 9 +-
plugins/build-tools/gbp-build-workbench-addin.c | 146 +---
plugins/build-tools/theme/Adwaita-dark.css | 1 +
plugins/build-tools/theme/Adwaita-shared.css | 82 ++
plugins/build-tools/theme/Adwaita.css | 1 +
plugins/build-tools/theme/shared.css | 44 -
plugins/vala-pack/Makefile.am | 8 +
plugins/vala-pack/ide-vala-index.vala | 4 +-
plugins/xdg-app/Makefile.am | 40 +
plugins/xdg-app/configure.ac | 24 +
plugins/xdg-app/gbp-xdg-plugin.c | 30 +
plugins/xdg-app/gbp-xdg-runtime-provider.c | 250 ++++++
plugins/xdg-app/gbp-xdg-runtime-provider.h | 32 +
plugins/xdg-app/gbp-xdg-runtime.c | 317 ++++++++
plugins/xdg-app/gbp-xdg-runtime.h | 32 +
plugins/xdg-app/xdg-app.plugin | 7 +
81 files changed, 7961 insertions(+), 1208 deletions(-)
---
diff --git a/.buildconfig b/.buildconfig
new file mode 100644
index 0000000..8c12c77
--- /dev/null
+++ b/.buildconfig
@@ -0,0 +1,36 @@
+# .buildconfig contains information required for Builder to
+# bootstrap the project using autotools.
+
+[debug]
+name=Builder (Debug)
+config-opts=--enable-tracing --enable-debug --enable-tests
+prefix=/app
+device=local
+runtime=xdg-app:org.gnome.Sdk/master/x86_64
+
+
+[debug.environment]
+
+
+[release]
+name=Builder (Release)
+config-opts=--enable-debug=minimum --enable-gtk-doc
+prefix=/app
+device=local
+runtime=xdg-app:org.gnome.Sdk/master/x86_64
+
+
+
+[release.environment]
+
+[host]
+name=Builder (Host)
+device=local
+runtime=host
+config-opts=--enable-tracing --enable-debug --enable-tests
+prefix=/opt/gnome
+
+
+default=true
+
+[host.environment]
diff --git a/Makefile.am b/Makefile.am
index 913bfe6..7300a6e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -72,7 +72,7 @@ run:
GB_IN_TREE_PLUGINS=1 \
GB_IN_TREE_FONTS=1 \
GB_IN_TREE_STYLE_SCHEMES=1 \
- GI_TYPELIB_PATH="libide:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+ GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
GOBJECT_DEBUG=instance-count \
PATH=$(top_builddir)/src:${PATH} \
$(LIBTOOL) --mode=execute gdb -ex run --args src/gnome-builder -vvvv -s
@@ -82,7 +82,7 @@ strace:
GB_IN_TREE_PLUGINS=1 \
GB_IN_TREE_FONTS=1 \
GB_IN_TREE_STYLE_SCHEMES=1 \
- GI_TYPELIB_PATH="libide:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+ GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
GOBJECT_DEBUG=instance-count \
PATH=$(top_builddir)/src:${PATH} \
$(LIBTOOL) --mode=execute strace -T src/gnome-builder -vvvv -s
@@ -92,7 +92,7 @@ debug:
GB_IN_TREE_PLUGINS=1 \
GB_IN_TREE_FONTS=1 \
GB_IN_TREE_STYLE_SCHEMES=1 \
- GI_TYPELIB_PATH="libide:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+ GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
G_DEBUG=fatal-criticals \
GOBJECT_DEBUG=instance-count \
PATH=$(top_builddir)/src:${PATH} \
@@ -103,7 +103,7 @@ valgrind:
GB_IN_TREE_PLUGINS=1 \
GB_IN_TREE_FONTS=1 \
GB_IN_TREE_STYLE_SCHEMES=1 \
- GI_TYPELIB_PATH="libide:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+ GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
G_DEBUG=fatal-criticals \
G_SLICE=always-malloc \
PATH=$(top_builddir)/src:${PATH} \
diff --git a/configure.ac b/configure.ac
index ac0fd1f..c49a306 100644
--- a/configure.ac
+++ b/configure.ac
@@ -215,7 +215,6 @@ enable_python_scripting=no
AS_IF([test "x$have_pygobject" = "xyes"],[
AM_PATH_PYTHON([3.2.3])
AC_PATH_TOOL(PYTHON3_CONFIG, "python3-config")
-
AS_IF([test -z "${PYTHON3_CONFIG}"],[
AC_MSG_RESULT([Failed to locate python3-config.])
],[
@@ -261,6 +260,7 @@ m4_include([plugins/sysmon/configure.ac])
m4_include([plugins/todo/configure.ac])
m4_include([plugins/terminal/configure.ac])
m4_include([plugins/vala-pack/configure.ac])
+m4_include([plugins/xdg-app/configure.ac])
m4_include([plugins/xml-pack/configure.ac])
@@ -504,6 +504,7 @@ echo " Symbol Tree .......................... : ${enable_symbol_tree_plugin}"
echo " Todo ................................. : ${enable_todo_plugin}"
echo " Terminal ............................. : ${enable_terminal_plugin}"
echo " Vala Language Pack ................... : ${enable_vala_pack_plugin}"
+echo " Xdg-App .............................. : ${enable_xdg_app_plugin}"
echo " XML Language Pack .................... : ${enable_xml_pack_plugin}"
echo ""
echo " Templates"
diff --git a/data/icons/hicolor/icons.gresource.xml b/data/icons/hicolor/icons.gresource.xml
index 3bf88b0..981c129 100644
--- a/data/icons/hicolor/icons.gresource.xml
+++ b/data/icons/hicolor/icons.gresource.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/builder/icons">
+ <file>scalable/actions/builder-build-configure-symbolic.svg</file>
<file>scalable/actions/builder-move-left-symbolic.svg</file>
<file>scalable/actions/builder-move-right-symbolic.svg</file>
<file>scalable/actions/builder-split-tab-left-symbolic.svg</file>
diff --git a/data/icons/hicolor/scalable/actions/builder-build-configure-symbolic.svg
b/data/icons/hicolor/scalable/actions/builder-build-configure-symbolic.svg
new file mode 100644
index 0000000..63a660b
--- /dev/null
+++ b/data/icons/hicolor/scalable/actions/builder-build-configure-symbolic.svg
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/'
sodipodi:docname='builder-build-configure-symbolic.svg' height='16' id='svg7384'
xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape'
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg'
version='1.1' inkscape:version='0.91 r13725' width='16' xmlns='http://www.w3.org/2000/svg'>
+ <metadata id='metadata90'>
+ <rdf:RDF>
+ <cc:Work rdf:about=''>
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/>
+ <dc:title>Gnome Symbolic Icon Theme</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview inkscape:bbox-nodes='true' inkscape:bbox-paths='true' bordercolor='#666666'
borderopacity='1' inkscape:current-layer='layer2' inkscape:cx='-435.73272' inkscape:cy='99.387534'
gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88'
inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#555753'
inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true'
inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true'
inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false'
inkscape:snap-to-guides='true' inkscape:window-height='1376' inkscape:window-maximized='1'
inkscape:window-width='2560' inkscape:window-x='0' inkscape:window-y='27' inkscape:zoom='1'>
+ <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='40.00005' originy='-72.989176'
snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/>
+ </sodipodi:namedview>
+ <title id='title9167'>Gnome Symbolic Icon Theme</title>
+ <defs id='defs7386'/>
+ <g inkscape:groupmode='layer' id='layer2' inkscape:label='actions' style='display:inline'
transform='translate(40.00005,72.989176)'>
+
+ <path inkscape:connector-curvature='0' d='m -38.935547,-70.953125 0,2.960937 5.966797,0 0,-2.960937
-5.966797,0 z m 6.96875,0 0,2.960937 5.966797,0 0,-2.960937 -5.966797,0 z M -39,-66.960938 -39,-64 l
1.998047,0 0,-2.960938 -1.998047,0 z m 3.033203,0 0,2.960938 2.574219,0 A 5.5000052,5.5000052 0 0 1
-30,-66.789062 l 0,-0.171876 -5.966797,0 z m 8.09375,0 A 5.5000052,5.5000052 0 0 1 -26,-66.392578 l
0,-0.56836 -1.873047,0 z m -11.0625,4.101563 0,2.960937 5.179688,0 A 5.5000052,5.5000052 0 0 1 -34,-61.5 a
5.5000052,5.5000052 0 0 1 0.177734,-1.359375 l -5.113281,0 z' id='rect7417'
style='color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1,
2;stroke-d
ashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new'/>
+ <path inkscape:connector-curvature='0' d='m -28.56095,-65.068511 c -0.40656,0 -0.79288,0.0924
-1.15625,0.21875 l 1.90625,1.90625 c 0.39436,0.39326 0.39436,1.01297 0,1.40625 l -0.71875,0.75 c
-0.39436,0.39327 -1.0119,0.39327 -1.40625,0 l -1.9375,-1.9375 c -0.12669,0.36235 -0.21875,0.75081
-0.21875,1.15625 0,1.94058 1.58533,3.53125 3.53125,3.53125 0.40656,0 0.79288,-0.0924 1.15625,-0.21875 l
1.21875,1.1875 c 0.021,-2.6e-4 0.0415,-2.6e-4 0.0625,0 l 2.09375,-2.09375 -1.21875,-1.21875 c 0.1267,-0.36235
0.21875,-0.78205 0.21875,-1.1875 0,-1.94057 -1.58534,-3.5 -3.53125,-3.5 z' id='path3908-6'
sodipodi:nodetypes='sccccccsscccccss'
style='color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progress
ion:tb;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate'/>
+ </g>
+ <g inkscape:groupmode='layer' id='layer9' inkscape:label='apps' style='display:inline'
transform='translate(-201.00015,-144.01082)'/>
+ <g inkscape:groupmode='layer' id='layer1' inkscape:label='autocomplete'
transform='translate(40.00005,72.989176)'/>
+</svg>
diff --git a/data/icons/src/symbolic.svg b/data/icons/src/symbolic.svg
index a0a5700..d749c45 100644
--- a/data/icons/src/symbolic.svg
+++ b/data/icons/src/symbolic.svg
@@ -41,8 +41,8 @@
id="namedview88"
showgrid="false"
inkscape:zoom="1"
- inkscape:cx="-104.41643"
- inkscape:cy="91.160423"
+ inkscape:cx="-477.73277"
+ inkscape:cy="172.37671"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
@@ -332,6 +332,33 @@
y="-301.01355"
transform="scale(1,-1)" />
</g>
+ <g
+ style="display:inline;opacity:1"
+ transform="matrix(0,-1,-1,0,271.99995,-36.989426)"
+ inkscape:label="builder-build-configure"
+ id="g7395">
+ <rect
+
style="color:#bebebe;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:2;marker:none"
+ id="rect7397"
+ width="16"
+ height="16"
+ x="19.99975"
+ y="296" />
+ <g
+ id="g7399"
+ style="opacity:0.25" />
+ <path
+
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1,
2;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new"
+ d="M -38.935547 -70.953125 L -38.935547 -67.992188 L -32.96875 -67.992188 L -32.96875 -70.953125 L
-38.935547 -70.953125 z M -31.966797 -70.953125 L -31.966797 -67.992188 L -26 -67.992188 L -26 -70.953125 L
-31.966797 -70.953125 z M -39 -66.960938 L -39 -64 L -37.001953 -64 L -37.001953 -66.960938 L -39 -66.960938
z M -35.966797 -66.960938 L -35.966797 -64 L -33.392578 -64 A 5.5000052 5.5000052 0 0 1 -30 -66.789062 L -30
-66.960938 L -35.966797 -66.960938 z M -27.873047 -66.960938 A 5.5000052 5.5000052 0 0 1 -26 -66.392578 L -26
-66.960938 L -27.873047 -66.960938 z M -38.935547 -62.859375 L -38.935547 -59.898438 L -33.755859 -59.898438
A 5.5000052 5.5000052 0 0 1 -34 -61.5 A 5.5000052 5.5000052 0 0 1 -33.822266 -62.859375 L -38.935547
-62.859375 z "
+ transform="matrix(0,-1,-1,0,-36.989426,271.99995)"
+ id="rect7417" />
+ <path
+ sodipodi:nodetypes="sccccccsscccccss"
+ inkscape:connector-curvature="0"
+ id="path3908-6"
+ d="m 28.079085,300.5609 c 0,0.40656 -0.0924,0.79288 -0.21875,1.15625 l -1.90625,-1.90625 c
-0.39326,-0.39436 -1.01297,-0.39436 -1.40625,0 l -0.75,0.71875 c -0.39327,0.39436 -0.39327,1.0119 0,1.40625 l
1.9375,1.9375 c -0.36235,0.12669 -0.75081,0.21875 -1.15625,0.21875 -1.94058,0 -3.53125,-1.58533
-3.53125,-3.53125 0,-0.40656 0.0924,-0.79288 0.21875,-1.15625 l -1.1875,-1.21875 c 2.6e-4,-0.021
2.6e-4,-0.0415 0,-0.0625 l 2.09375,-2.09375 1.21875,1.21875 c 0.36235,-0.1267 0.78205,-0.21875
1.1875,-0.21875 1.94057,0 3.5,1.58534 3.5,3.53125 z"
+
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
/>
+ </g>
</g>
<g
inkscape:groupmode="layer"
diff --git a/data/theme/Adwaita-shared.css b/data/theme/Adwaita-shared.css
index 4519999..27d952a 100644
--- a/data/theme/Adwaita-shared.css
+++ b/data/theme/Adwaita-shared.css
@@ -2,6 +2,15 @@
/*
+ * Round buttons, similar to control-center.
+ */
+button.round-button {
+ border-radius: 20px;
+ -gtk-outline-radius: 20px;
+}
+
+
+/*
* Titlebar adjustments for workbench
*
* This is needed due to our placement of headerbar inside of a
@@ -71,6 +80,9 @@ omnisearchdisplay omnisearchgroup omnisearchrow:selected {
* preferences perspective, including groups, rows,
* sidebar, etc.
*/
+workbench preferences stacksidebar list {
+ border-right: 1px solid alpha(@borders, 0.4);
+}
workbench preferences preferencesgroup list row {
padding: 10px;
border-bottom: 1px solid alpha(@borders, 0.2);
@@ -210,7 +222,7 @@ layoutpane .pane-separator {
popover list row {
padding: 6px;
}
-popover list row button {
+popover list row button:not(.round-button) {
background: transparent;
border: none;
box-shadow: none;
@@ -218,11 +230,11 @@ popover list row button {
padding: 0;
opacity: 0.25;
}
-popover list row:selected button,
-popover list row:selected button:hover {
+popover list row:selected button:not(.round-button),
+popover list row:selected button:not(.round-button):hover {
color: @theme_selected_fg_color;
}
-popover list row button:hover {
+popover list row button:not(.round-button):hover {
opacity: 1;
}
diff --git a/data/ui/ide-environment-editor-row.ui b/data/ui/ide-environment-editor-row.ui
new file mode 100644
index 0000000..1a30ab7
--- /dev/null
+++ b/data/ui/ide-environment-editor-row.ui
@@ -0,0 +1,48 @@
+<interface>
+ <template class="IdeEnvironmentEditorRow" parent="GtkListBoxRow">
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkEntry" id="key_entry">
+ <property name="has-frame">false</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="eq_label">
+ <property name="label">=</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="value_entry">
+ <property name="hexpand">true</property>
+ <property name="has-frame">false</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="delete_button">
+ <property name="visible">true</property>
+ <style>
+ <class name="image-button"/>
+ <class name="flat"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="icon-name">list-remove-symbolic</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/data/ui/ide-omni-search-row.ui b/data/ui/ide-omni-search-row.ui
index bfb167c..f9b08fa 100644
--- a/data/ui/ide-omni-search-row.ui
+++ b/data/ui/ide-omni-search-row.ui
@@ -15,6 +15,7 @@
</child>
<child>
<object class="GtkLabel" id="title">
+ <property name="ellipsize">middle</property>
<property name="visible">true</property>
<property name="xalign">0.0</property>
</object>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 657d914..054a895 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -61,6 +61,10 @@ libide_1_0_la_public_sources = \
ide-completion-provider.h \
ide-completion-results.c \
ide-completion-results.h \
+ ide-configuration.c \
+ ide-configuration.h \
+ ide-configuration-manager.c \
+ ide-configuration-manager.h \
ide-context.c \
ide-context.h \
ide-debugger.c \
@@ -83,6 +87,14 @@ libide_1_0_la_public_sources = \
ide-diagnostics.h \
ide-enums.c \
ide-enums.h \
+ ide-environment.c \
+ ide-environment.h \
+ ide-environment-editor.c \
+ ide-environment-editor.h \
+ ide-environment-editor-row.c \
+ ide-environment-editor-row.h \
+ ide-environment-variable.c \
+ ide-environment-variable.h \
ide-executable.c \
ide-executable.h \
ide-executer.c \
@@ -150,6 +162,12 @@ libide_1_0_la_public_sources = \
ide-project.h \
ide-recent-projects.c \
ide-recent-projects.h \
+ ide-runtime.c \
+ ide-runtime.h \
+ ide-runtime-manager.c \
+ ide-runtime-manager.h \
+ ide-runtime-provider.c \
+ ide-runtime-provider.h \
ide-refactory.c \
ide-refactory.h \
ide-script-manager.c \
@@ -188,6 +206,8 @@ libide_1_0_la_public_sources = \
ide-source-view-mode.h \
ide-source-view.c \
ide-source-view.h \
+ ide-subprocess-launcher.c \
+ ide-subprocess-launcher.h \
ide-symbol-resolver.c \
ide-symbol-resolver.h \
ide-symbol.c \
@@ -393,6 +413,8 @@ libide_1_0_la_SOURCES = \
util/ide-list-inline.h \
util/ide-pango.c \
util/ide-pango.h \
+ util/ide-posix.c \
+ util/ide-posix.h \
util/ide-rgba.c \
util/ide-rgba.h \
util/ide-text-util.c \
@@ -539,6 +561,7 @@ INTROSPECTION_GIRS =
INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) --warn-all
INTROSPECTION_COMPILER_ARGS = \
--includedir=$(srcdir) \
+ --includedir=$(top_builddir)/contrib/egg \
--includedir=$(top_builddir)/contrib/tmpl
introspection_sources = \
@@ -554,6 +577,7 @@ Ide_1_0_gir_LIBS = \
$(top_builddir)/contrib/tmpl/libtemplate-glib-1.0.la
Ide_1_0_gir_FILES = $(introspection_sources)
Ide_1_0_gir_SCANNERFLAGS = \
+ --include-uninstalled=$(top_builddir)/contrib/egg/Egg-1.0.gir \
--include-uninstalled=$(top_builddir)/contrib/tmpl/Template-1.0.gir \
--c-include="ide.h" \
-n Ide \
@@ -577,11 +601,21 @@ libide-1.0.vapi: Ide-1.0.gir
VAPIGEN_VAPIS = libide-1.0.vapi
-libide_1_0_vapi_DEPS = gio-2.0 gtk+-3.0 gtksourceview-3.0 libpeas-1.0 template-glib-1.0
+libide_1_0_vapi_DEPS = \
+ gio-2.0 \
+ gtk+-3.0 \
+ gtksourceview-3.0 \
+ libpeas-1.0 \
+ template-glib-1.0 \
+ egg-private
libide_1_0_vapi_METADATADIRS = $(srcdir)
libide_1_0_vapi_FILES = Ide-1.0.gir
-libide_1_0_vapi_VAPIDIRS = $(top_builddir)/contrib/tmpl
-libide_1_0_vapi_GIRDIRS = $(top_builddir)/contrib/tmpl
+libide_1_0_vapi_VAPIDIRS = \
+ $(top_builddir)/contrib/egg \
+ $(top_builddir)/contrib/tmpl
+libide_1_0_vapi_GIRDIRS = \
+ $(top_builddir)/contrib/egg \
+ $(top_builddir)/contrib/tmpl
libide-1.0.deps: Makefile
$(AM_V_GEN) echo $(libide_1_0_vapi_DEPS) | tr ' ' '\n' > $@
diff --git a/libide/ide-build-system.c b/libide/ide-build-system.c
index 90b7f33..e2cf3b3 100644
--- a/libide/ide-build-system.c
+++ b/libide/ide-build-system.c
@@ -19,10 +19,14 @@
#include <glib/gi18n.h>
#include "ide-build-system.h"
+#include "ide-configuration.h"
#include "ide-context.h"
#include "ide-device.h"
+#include "ide-device-manager.h"
#include "ide-file.h"
#include "ide-object.h"
+#include "ide-runtime.h"
+#include "ide-runtime-manager.h"
typedef struct
{
@@ -104,9 +108,28 @@ ide_build_system_get_build_flags_finish (IdeBuildSystem *self,
return g_new0 (gchar*, 1);
}
+static IdeBuilder *
+ide_build_system_real_get_builder (IdeBuildSystem *self,
+ IdeConfiguration *configuration,
+ GError **error)
+{
+ g_assert (IDE_IS_BUILD_SYSTEM (self));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("%s() is not supported on %s build system."),
+ G_STRFUNC, g_type_name (G_TYPE_FROM_INSTANCE (self)));
+
+ return NULL;
+}
+
static void
ide_build_system_default_init (IdeBuildSystemInterface *iface)
{
+ iface->get_builder = ide_build_system_real_get_builder;
+
properties [PROP_PROJECT_FILE] =
g_param_spec_object ("project-file",
"Project File",
@@ -196,43 +219,23 @@ ide_build_system_new_finish (GAsyncResult *result,
/**
* ide_build_system_get_builder:
* @system: The #IdeBuildSystem to perform the build.
- * @config: (nullable): The configuration options for the build.
- * @device: The #IdeDevice the result should be able to run on.
+ * @configuration: An #IdeConfiguration.
*
- * This function should return an #IdeBuilder that can be used to perform a
- * build of the project using the configuration specified. @device may be
- * a non-local device, for which cross-compilation may be necessary.
+ * This function returns an #IdeBuilder that can be used to perform a
+ * build of the project using the configuration specified.
+ *
+ * See ide_builder_build_async() for more information.
*
* Returns: (transfer full): An #IdeBuilder or %NULL and @error is set.
*/
IdeBuilder *
-ide_build_system_get_builder (IdeBuildSystem *system,
- GKeyFile *config,
- IdeDevice *device,
- GError **error)
+ide_build_system_get_builder (IdeBuildSystem *system,
+ IdeConfiguration *configuration,
+ GError **error)
{
- IdeBuildSystemInterface *iface;
- IdeBuilder *ret = NULL;
- g_autoptr(GKeyFile) local = NULL;
-
g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (system), NULL);
- g_return_val_if_fail (IDE_IS_DEVICE (device), NULL);
-
- if (config == NULL)
- config = local = g_key_file_new ();
-
- iface = IDE_BUILD_SYSTEM_GET_IFACE (system);
-
- if (iface->get_builder)
- ret = iface->get_builder (system, config, device, error);
- else
- g_set_error (error,
- G_IO_ERROR,
- G_IO_ERROR_NOT_SUPPORTED,
- _("%s() is not supported on %s build system."),
- G_STRFUNC,
- g_type_name (G_TYPE_FROM_INSTANCE (system)));
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (configuration), NULL);
- return ret;
+ return IDE_BUILD_SYSTEM_GET_IFACE (system)->get_builder (system, configuration, error);
}
diff --git a/libide/ide-build-system.h b/libide/ide-build-system.h
index ef286c6..3cb9e98 100644
--- a/libide/ide-build-system.h
+++ b/libide/ide-build-system.h
@@ -35,8 +35,7 @@ struct _IdeBuildSystemInterface
gint (*get_priority) (IdeBuildSystem *system);
IdeBuilder *(*get_builder) (IdeBuildSystem *system,
- GKeyFile *config,
- IdeDevice *device,
+ IdeConfiguration *configuration,
GError **error);
void (*get_build_flags_async) (IdeBuildSystem *self,
IdeFile *file,
@@ -65,8 +64,7 @@ void ide_build_system_new_async (IdeContext *c
IdeBuildSystem *ide_build_system_new_finish (GAsyncResult *result,
GError **error);
IdeBuilder *ide_build_system_get_builder (IdeBuildSystem *system,
- GKeyFile *config,
- IdeDevice *device,
+ IdeConfiguration *configuration,
GError **error);
G_END_DECLS
diff --git a/libide/ide-builder.c b/libide/ide-builder.c
index 520e177..982f0d2 100644
--- a/libide/ide-builder.c
+++ b/libide/ide-builder.c
@@ -20,13 +20,90 @@
#include "ide-build-result.h"
#include "ide-builder.h"
+#include "ide-configuration.h"
-G_DEFINE_ABSTRACT_TYPE (IdeBuilder, ide_builder, IDE_TYPE_OBJECT)
+typedef struct
+{
+ IdeConfiguration *configuration;
+} IdeBuilderPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (IdeBuilder, ide_builder, IDE_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_CONFIGURATION,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+/**
+ * ide_builder_get_configuration:
+ * @self: An #IdeBuilder.
+ *
+ * Gets the configuration to use for the builder.
+ *
+ * Returns: (transfer none): An #IdeConfiguration.
+ */
+IdeConfiguration *
+ide_builder_get_configuration (IdeBuilder *self)
+{
+ IdeBuilderPrivate *priv = ide_builder_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_BUILDER (self), NULL);
+
+ return priv->configuration;
+}
+
+static void
+ide_builder_set_configuration (IdeBuilder *self,
+ IdeConfiguration *configuration)
+{
+ IdeBuilderPrivate *priv = ide_builder_get_instance_private (self);
+
+ g_assert (IDE_IS_BUILDER (self));
+ g_assert (!configuration || IDE_IS_CONFIGURATION (configuration));
+
+ if (g_set_object (&priv->configuration, configuration))
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONFIGURATION]);
+}
+
+static void
+ide_builder_real_build_async (IdeBuilder *self,
+ IdeBuilderBuildFlags flags,
+ IdeBuildResult **result,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (IDE_IS_BUILDER (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_assert (!result || *result == NULL);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("%s does not support building"),
+ G_OBJECT_TYPE_NAME (self));
+}
+
+static IdeBuildResult *
+ide_builder_real_build_finish (IdeBuilder *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (IDE_IS_BUILDER (self));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
/**
* ide_builder_build_async:
* @result: (out) (transfer none): A location for an #IdeBuildResult.
- *
*/
void
ide_builder_build_async (IdeBuilder *builder,
@@ -36,29 +113,13 @@ ide_builder_build_async (IdeBuilder *builder,
GAsyncReadyCallback callback,
gpointer user_data)
{
- IdeBuilderClass *klass;
-
g_return_if_fail (IDE_IS_BUILDER (builder));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
- if (result)
+ if (result != NULL)
*result = NULL;
- klass = IDE_BUILDER_GET_CLASS (builder);
-
- if (klass->build_async)
- {
- klass->build_async (builder, flags, result, cancellable, callback, user_data);
- return;
- }
-
- g_warning (_("%s does not implement build_async()"),
- g_type_name (G_TYPE_FROM_INSTANCE (builder)));
-
- g_task_report_new_error (builder, callback, user_data,
- ide_builder_build_async,
- G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
- _("No implementation of build_async()"));
+ IDE_BUILDER_GET_CLASS (builder)->build_async (builder, flags, result, cancellable, callback, user_data);
}
/**
@@ -73,18 +134,12 @@ ide_builder_build_finish (IdeBuilder *builder,
GAsyncResult *result,
GError **error)
{
- IdeBuilderClass *klass;
IdeBuildResult *ret = NULL;
g_return_val_if_fail (IDE_IS_BUILDER (builder), NULL);
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
- klass = IDE_BUILDER_GET_CLASS (builder);
-
- if (klass->build_finish)
- ret = klass->build_finish (builder, result, error);
- else if (G_IS_TASK (result))
- ret = g_task_propagate_pointer (G_TASK (result), error);
+ ret = IDE_BUILDER_GET_CLASS (builder)->build_finish (builder, result, error);
g_return_val_if_fail (!ret || IDE_IS_BUILD_RESULT (ret), NULL);
@@ -92,8 +147,74 @@ ide_builder_build_finish (IdeBuilder *builder,
}
static void
+ide_builder_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBuilder *self = IDE_BUILDER(object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIGURATION:
+ g_value_set_object (value, ide_builder_get_configuration (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+ide_builder_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBuilder *self = IDE_BUILDER(object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIGURATION:
+ ide_builder_set_configuration (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+ide_builder_finalize (GObject *object)
+{
+ IdeBuilder *self = (IdeBuilder *)object;
+ IdeBuilderPrivate *priv = ide_builder_get_instance_private (self);
+
+ g_clear_object (&priv->configuration);
+
+ G_OBJECT_CLASS (ide_builder_parent_class)->finalize (object);
+}
+
+static void
ide_builder_class_init (IdeBuilderClass *klass)
{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_builder_finalize;
+ object_class->get_property = ide_builder_get_property;
+ object_class->set_property = ide_builder_set_property;
+
+ klass->build_async = ide_builder_real_build_async;
+ klass->build_finish = ide_builder_real_build_finish;
+
+ properties [PROP_CONFIGURATION] =
+ g_param_spec_object ("configuration",
+ "Configuration",
+ "Configuration",
+ IDE_TYPE_CONFIGURATION,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
}
static void
diff --git a/libide/ide-builder.h b/libide/ide-builder.h
index 0232c6f..36604e3 100644
--- a/libide/ide-builder.h
+++ b/libide/ide-builder.h
@@ -29,11 +29,11 @@ G_DECLARE_DERIVABLE_TYPE (IdeBuilder, ide_builder, IDE, BUILDER, IdeObject)
typedef enum
{
- IDE_BUILDER_BUILD_FLAGS_NONE = 0,
- IDE_BUILDER_BUILD_FLAGS_FORCE_REBUILD = 1 << 0,
-
- /* TODO: this belongs as a vfunc instead */
- IDE_BUILDER_BUILD_FLAGS_CLEAN = 1 << 1,
+ IDE_BUILDER_BUILD_FLAGS_NONE = 0,
+ IDE_BUILDER_BUILD_FLAGS_FORCE_BOOTSTRAP = 1 << 0,
+ IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN = 1 << 1,
+ IDE_BUILDER_BUILD_FLAGS_NO_BUILD = 1 << 2,
+ IDE_BUILDER_BUILD_FLAGS_NO_CONFIGURE = 1 << 3,
} IdeBuilderBuildFlags;
struct _IdeBuilderClass
@@ -51,15 +51,16 @@ struct _IdeBuilderClass
GError **error);
};
-void ide_builder_build_async (IdeBuilder *builder,
- IdeBuilderBuildFlags flags,
- IdeBuildResult **result,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-IdeBuildResult *ide_builder_build_finish (IdeBuilder *builder,
- GAsyncResult *result,
- GError **error);
+IdeConfiguration *ide_builder_get_configuration (IdeBuilder *self);
+void ide_builder_build_async (IdeBuilder *builder,
+ IdeBuilderBuildFlags flags,
+ IdeBuildResult **result,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IdeBuildResult *ide_builder_build_finish (IdeBuilder *builder,
+ GAsyncResult *result,
+ GError **error);
G_END_DECLS
diff --git a/libide/ide-configuration-manager.c b/libide/ide-configuration-manager.c
new file mode 100644
index 0000000..891790e
--- /dev/null
+++ b/libide/ide-configuration-manager.c
@@ -0,0 +1,736 @@
+/* ide-configuration-manager.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-configuration-manager"
+
+#include <glib/gi18n.h>
+
+#include "ide-configuration.h"
+#include "ide-configuration-manager.h"
+#include "ide-context.h"
+#include "ide-debug.h"
+#include "ide-environment.h"
+#include "ide-macros.h"
+#include "ide-vcs.h"
+
+#define DOT_BUILD_CONFIG ".buildconfig"
+#define WRITEBACK_TIMEOUT_SECS 2
+
+struct _IdeConfigurationManager
+{
+ GObject parent_instance;
+
+ GPtrArray *configurations;
+ IdeConfiguration *current;
+ GKeyFile *key_file;
+
+ gulong writeback_handler;
+};
+
+static void async_initable_iface_init (GAsyncInitableIface *iface);
+static void list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeConfigurationManager, ide_configuration_manager, IDE_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init))
+
+enum {
+ PROP_0,
+ PROP_CURRENT,
+ PROP_CURRENT_DISPLAY_NAME,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+load_string (IdeConfiguration *configuration,
+ GKeyFile *key_file,
+ const gchar *group,
+ const gchar *key,
+ const gchar *property)
+{
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+ g_assert (key_file != NULL);
+ g_assert (group != NULL);
+ g_assert (key != NULL);
+
+ if (g_key_file_has_key (key_file, group, key, NULL))
+ {
+ g_auto(GValue) value = G_VALUE_INIT;
+
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_take_string (&value, g_key_file_get_string (key_file, group, key, NULL));
+ g_object_set_property (G_OBJECT (configuration), property, &value);
+ }
+}
+
+static void
+load_environ (IdeConfiguration *configuration,
+ GKeyFile *key_file,
+ const gchar *group)
+{
+ IdeEnvironment *environment;
+ g_auto(GStrv) keys = NULL;
+
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+ g_assert (key_file != NULL);
+ g_assert (group != NULL);
+
+ environment = ide_configuration_get_environment (configuration);
+ keys = g_key_file_get_keys (key_file, group, NULL, NULL);
+
+ if (keys != NULL)
+ {
+ guint i;
+
+ for (i = 0; keys [i]; i++)
+ {
+ g_autofree gchar *value = NULL;
+
+ value = g_key_file_get_string (key_file, group, keys [i], NULL);
+
+ if (value != NULL)
+ ide_environment_setenv (environment, keys [i], value);
+ }
+ }
+}
+
+static gboolean
+ide_configuration_manager_load (IdeConfigurationManager *self,
+ GKeyFile *key_file,
+ const gchar *group,
+ GError **error)
+{
+ g_autoptr(IdeConfiguration) configuration = NULL;
+ g_autofree gchar *env_group = NULL;
+ IdeContext *context;
+
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+ g_assert (key_file != NULL);
+ g_assert (group != NULL);
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+
+ configuration = g_object_new (IDE_TYPE_CONFIGURATION,
+ "id", group,
+ "context", context,
+ NULL);
+
+ load_string (configuration, key_file, group, "config-opts", "config-opts");
+ load_string (configuration, key_file, group, "device", "device-id");
+ load_string (configuration, key_file, group, "name", "display-name");
+ load_string (configuration, key_file, group, "runtime", "runtime-id");
+ load_string (configuration, key_file, group, "prefix", "prefix");
+
+ env_group = g_strdup_printf ("%s.environment", group);
+
+ if (g_key_file_has_group (key_file, env_group))
+ load_environ (configuration, key_file, env_group);
+
+ ide_configuration_set_dirty (configuration, FALSE);
+
+ ide_configuration_manager_add (self, configuration);
+
+ if (g_key_file_get_boolean (key_file, group, "default", NULL))
+ ide_configuration_manager_set_current (self, configuration);
+
+ return TRUE;
+}
+
+static gboolean
+ide_configuration_manager_restore (IdeConfigurationManager *self,
+ GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *contents = NULL;
+ g_auto(GStrv) groups = NULL;
+ gsize length = 0;
+ guint i;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+ g_assert (self->key_file == NULL);
+ g_assert (G_IS_FILE (file));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ self->key_file = g_key_file_new ();
+
+ if (!g_file_load_contents (file, cancellable, &contents, &length, NULL, error))
+ IDE_RETURN (FALSE);
+
+ if (!g_key_file_load_from_data (self->key_file,
+ contents,
+ length,
+ G_KEY_FILE_KEEP_COMMENTS,
+ error))
+ IDE_RETURN (FALSE);
+
+ groups = g_key_file_get_groups (self->key_file, NULL);
+
+ for (i = 0; groups [i]; i++)
+ {
+ if (g_str_has_suffix (groups [i], ".environment"))
+ continue;
+
+ if (!ide_configuration_manager_load (self, self->key_file, groups [i], error))
+ IDE_RETURN (FALSE);
+ }
+
+ IDE_RETURN (TRUE);
+}
+
+static void
+ide_configuration_manager_save_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = user_data;
+ GError *error = NULL;
+ GFile *file = (GFile *)object;
+
+ g_assert (G_IS_FILE (file));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ if (!g_file_replace_contents_finish (file, result, NULL, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+void
+ide_configuration_manager_save_async (IdeConfigurationManager *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GHashTable) group_names = NULL;
+ g_autoptr(GTask) task = NULL;
+ g_auto(GStrv) groups = NULL;
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GBytes) bytes = NULL;
+ gchar *data;
+ gsize length;
+ IdeContext *context;
+ IdeVcs *vcs;
+ GFile *workdir;
+ GError *error = NULL;
+ guint i;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ vcs = ide_context_get_vcs (context);
+ workdir = ide_vcs_get_working_directory (vcs);
+ file = g_file_get_child (workdir, DOT_BUILD_CONFIG);
+
+ /*
+ * NOTE:
+ *
+ * We keep the GKeyFile around from when we parsed .buildconfig, so that
+ * we can try to preserve comments and such when writing back.
+ *
+ * This means that we need to fill in all our known configuration
+ * sections, and then remove any that were removed since we were
+ * parsed it last.
+ */
+
+ if (self->key_file == NULL)
+ self->key_file = g_key_file_new ();
+
+ group_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ for (i = 0; i < self->configurations->len; i++)
+ {
+ IdeConfiguration *configuration = g_ptr_array_index (self->configurations, i);
+ IdeEnvironment *environment;
+ guint n_items;
+ guint j;
+ gchar *group;
+ gchar *group_environ;
+
+ group = g_strdup (ide_configuration_get_id (configuration));
+ group_environ = g_strdup_printf ("%s.environment", group);
+
+ /*
+ * Track our known group names, so we can remove missing names after
+ * we've updated the GKeyFile.
+ */
+ g_hash_table_insert (group_names, group, NULL);
+ g_hash_table_insert (group_names, group_environ, NULL);
+
+#define PERSIST_STRING_KEY(key, getter) \
+ g_key_file_set_string (self->key_file, group, key, \
+ ide_configuration_##getter (configuration))
+ PERSIST_STRING_KEY ("name", get_display_name);
+ PERSIST_STRING_KEY ("device", get_device_id);
+ PERSIST_STRING_KEY ("runtime", get_runtime_id);
+ PERSIST_STRING_KEY ("config-opts", get_config_opts);
+ PERSIST_STRING_KEY ("prefix", get_prefix);
+#undef PERSIST_STRING_KEY
+
+ if (configuration == self->current)
+ g_key_file_set_boolean (self->key_file, group, "default", TRUE);
+ else
+ g_key_file_remove_key (self->key_file, group, "default", NULL);
+
+ environment = ide_configuration_get_environment (configuration);
+
+ /*
+ * Remove all environment keys that are no longer specified in the
+ * environment. This allows us to just do a single pass of additions
+ * from the environment below.
+ */
+ if (g_key_file_has_group (self->key_file, group_environ))
+ {
+ g_auto(GStrv) keys = NULL;
+
+ if (NULL != (keys = g_key_file_get_keys (self->key_file, group_environ, NULL, NULL)))
+ {
+ for (j = 0; keys [j]; j++)
+ {
+ if (!ide_environment_getenv (environment, keys [j]))
+ g_key_file_remove_key (self->key_file, group_environ, keys [j], NULL);
+ }
+ }
+ }
+
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (environment));
+
+ for (j = 0; j < n_items; j++)
+ {
+ g_autoptr(IdeEnvironmentVariable) var = NULL;
+ const gchar *key;
+ const gchar *value;
+
+ var = g_list_model_get_item (G_LIST_MODEL (environment), j);
+ key = ide_environment_variable_get_key (var);
+ value = ide_environment_variable_get_value (var);
+
+ if (!ide_str_empty0 (key))
+ g_key_file_set_string (self->key_file, group_environ, key, value ?: "");
+ }
+ }
+
+ /*
+ * Now truncate any old groups in the keyfile.
+ */
+ if (NULL != (groups = g_key_file_get_groups (self->key_file, NULL)))
+ {
+ for (i = 0; groups [i]; i++)
+ {
+ if (!g_hash_table_contains (group_names, groups [i]))
+ g_key_file_remove_group (self->key_file, groups [i], NULL);
+ }
+ }
+
+ if (NULL == (data = g_key_file_to_data (self->key_file, &length, &error)))
+ {
+ g_task_return_error (task, error);
+ IDE_EXIT;
+ }
+
+ bytes = g_bytes_new_take (data, length);
+
+ g_file_replace_contents_bytes_async (file,
+ bytes,
+ NULL,
+ FALSE,
+ G_FILE_CREATE_NONE,
+ cancellable,
+ ide_configuration_manager_save_cb,
+ g_object_ref (task));
+
+ IDE_EXIT;
+}
+
+gboolean
+ide_configuration_manager_save_finish (IdeConfigurationManager *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION_MANAGER (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+ide_configuration_manager_do_writeback (gpointer data)
+{
+ IdeConfigurationManager *self = data;
+
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+
+ self->writeback_handler = 0;
+
+ ide_configuration_manager_save_async (self, NULL, NULL, NULL);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+ide_configuration_manager_queue_writeback (IdeConfigurationManager *self)
+{
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+
+ if (self->writeback_handler != 0)
+ g_source_remove (self->writeback_handler);
+
+ self->writeback_handler = g_timeout_add_seconds (WRITEBACK_TIMEOUT_SECS,
+ ide_configuration_manager_do_writeback,
+ self);
+}
+
+static void
+ide_configuration_manager_add_default (IdeConfigurationManager *self)
+{
+ g_autoptr(IdeConfiguration) config = NULL;
+ IdeContext *context;
+
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+
+ config = ide_configuration_new (context, "default", "local", "host");
+ ide_configuration_set_display_name (config, _("Default Configuration"));
+ ide_configuration_manager_add (self, config);
+
+ if (self->configurations->len == 1)
+ ide_configuration_manager_set_current (self, config);
+}
+
+/**
+ * ide_configuration_manager_get_configuration:
+ * @self: An #IdeConfigurationManager
+ * @id: The string identifier of the configuration
+ *
+ * Gets the #IdeConfiguration by id. See ide_configuration_get_id().
+ *
+ * Returns: (transfer none) (nullable): An #IdeConfiguration or %NULL if
+ * the configuration could not be found.
+ */
+IdeConfiguration *
+ide_configuration_manager_get_configuration (IdeConfigurationManager *self,
+ const gchar *id)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION_MANAGER (self), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+
+ for (guint i = 0; i < self->configurations->len; i++)
+ {
+ IdeConfiguration *configuration = g_ptr_array_index (self->configurations, i);
+
+ if (g_strcmp0 (id, ide_configuration_get_id (configuration)) == 0)
+ return configuration;
+ }
+
+ return NULL;
+}
+
+static void
+ide_configuration_manager_finalize (GObject *object)
+{
+ IdeConfigurationManager *self = (IdeConfigurationManager *)object;
+
+ ide_clear_source (&self->writeback_handler);
+ g_clear_pointer (&self->configurations, g_ptr_array_unref);
+ g_clear_pointer (&self->key_file, g_key_file_free);
+ g_clear_object (&self->current);
+
+ G_OBJECT_CLASS (ide_configuration_manager_parent_class)->finalize (object);
+}
+
+static void
+ide_configuration_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeConfigurationManager *self = IDE_CONFIGURATION_MANAGER (object);
+
+ switch (prop_id)
+ {
+ case PROP_CURRENT:
+ g_value_set_object (value, ide_configuration_manager_get_current (self));
+ break;
+
+ case PROP_CURRENT_DISPLAY_NAME:
+ {
+ IdeConfiguration *current = ide_configuration_manager_get_current (self);
+ g_value_set_string (value, ide_configuration_get_display_name (current));
+ break;
+ }
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+ide_configuration_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeConfigurationManager *self = IDE_CONFIGURATION_MANAGER (object);
+
+ switch (prop_id)
+ {
+ case PROP_CURRENT:
+ ide_configuration_manager_set_current (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+ide_configuration_manager_class_init (IdeConfigurationManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_configuration_manager_finalize;
+ object_class->get_property = ide_configuration_manager_get_property;
+ object_class->set_property = ide_configuration_manager_set_property;
+
+ properties [PROP_CURRENT] =
+ g_param_spec_object ("current",
+ "Current",
+ "The current configuration for the context",
+ IDE_TYPE_CONFIGURATION,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_CURRENT_DISPLAY_NAME] =
+ g_param_spec_string ("current-display-name",
+ "Current Display Name",
+ "The display name of the current configuration",
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_configuration_manager_init (IdeConfigurationManager *self)
+{
+ self->configurations = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+static GType
+ide_configuration_manager_get_item_type (GListModel *model)
+{
+ return IDE_TYPE_CONFIGURATION;
+}
+
+static guint
+ide_configuration_manager_get_n_items (GListModel *model)
+{
+ IdeConfigurationManager *self = (IdeConfigurationManager *)model;
+
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+
+ return self->configurations->len;
+}
+
+static gpointer
+ide_configuration_manager_get_item (GListModel *model,
+ guint position)
+{
+ IdeConfigurationManager *self = (IdeConfigurationManager *)model;
+
+ g_return_val_if_fail (IDE_IS_CONFIGURATION_MANAGER (self), NULL);
+ g_return_val_if_fail (position < self->configurations->len, NULL);
+
+ return g_object_ref (g_ptr_array_index (self->configurations, position));
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_item_type = ide_configuration_manager_get_item_type;
+ iface->get_n_items = ide_configuration_manager_get_n_items;
+ iface->get_item = ide_configuration_manager_get_item;
+}
+
+static void
+ide_configuration_manager_init_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ IdeConfigurationManager *self = source_object;
+ g_autoptr(GFile) settings_file = NULL;
+ IdeContext *context;
+ GError *error = NULL;
+ IdeVcs *vcs;
+ GFile *workdir;
+
+ g_assert (G_IS_TASK (task));
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ vcs = ide_context_get_vcs (context);
+ workdir = ide_vcs_get_working_directory (vcs);
+ settings_file = g_file_get_child (workdir, DOT_BUILD_CONFIG);
+
+ if (!g_file_query_exists (settings_file, cancellable) ||
+ !ide_configuration_manager_restore (self, settings_file, cancellable, &error))
+ ide_configuration_manager_add_default (self);
+
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_configuration_manager_init_async (GAsyncInitable *initable,
+ gint priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdeConfigurationManager *self = (IdeConfigurationManager *)initable;
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (G_IS_ASYNC_INITABLE (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_run_in_thread (task, ide_configuration_manager_init_worker);
+}
+
+static gboolean
+ide_configuration_manager_init_finish (GAsyncInitable *initable,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (initable));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *iface)
+{
+ iface->init_async = ide_configuration_manager_init_async;
+ iface->init_finish = ide_configuration_manager_init_finish;
+}
+
+void
+ide_configuration_manager_set_current (IdeConfigurationManager *self,
+ IdeConfiguration *current)
+{
+ g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (self));
+ g_return_if_fail (!current || IDE_IS_CONFIGURATION (current));
+
+ if (g_set_object (&self->current, current))
+ {
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CURRENT]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CURRENT_DISPLAY_NAME]);
+ }
+}
+
+/**
+ * ide_configuration_manager_get_current:
+ * @self: An #IdeConfigurationManager
+ *
+ * Gets the current configuration to use for building.
+ *
+ * Many systems allow you to pass a configuration in instead of relying on the
+ * default configuration. This sets the default configuration that various
+ * background items might use, such as tags builders which need to discover
+ * settings.
+ *
+ * Returns: (transfer none): An #IdeConfiguration
+ */
+IdeConfiguration *
+ide_configuration_manager_get_current (IdeConfigurationManager *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION_MANAGER (self), NULL);
+
+ if ((self->current == NULL) && (self->configurations->len > 0))
+ return g_ptr_array_index (self->configurations, 0);
+
+ return self->current;
+}
+
+static void
+ide_configuration_manager_changed (IdeConfigurationManager *self,
+ GParamSpec *pspec,
+ IdeConfiguration *configuration)
+{
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ ide_configuration_manager_queue_writeback (self);
+}
+
+void
+ide_configuration_manager_add (IdeConfigurationManager *self,
+ IdeConfiguration *configuration)
+{
+ guint position;
+
+ g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (self));
+ g_return_if_fail (IDE_IS_CONFIGURATION (configuration));
+
+ g_signal_connect_object (configuration,
+ "changed",
+ G_CALLBACK (ide_configuration_manager_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ position = self->configurations->len;
+ g_ptr_array_add (self->configurations, g_object_ref (configuration));
+ g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
+}
+
+void
+ide_configuration_manager_remove (IdeConfigurationManager *self,
+ IdeConfiguration *configuration)
+{
+ guint i;
+
+ g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (self));
+ g_return_if_fail (IDE_IS_CONFIGURATION (configuration));
+
+ for (i = 0; i < self->configurations->len; i++)
+ {
+ IdeConfiguration *item = g_ptr_array_index (self->configurations, i);
+
+ if (item == configuration)
+ {
+ g_ptr_array_remove_index (self->configurations, i);
+ g_list_model_items_changed (G_LIST_MODEL (self), i, 1, 0);
+ if (self->configurations->len == 0)
+ ide_configuration_manager_add_default (self);
+ if (self->current == configuration)
+ ide_configuration_manager_set_current (self, NULL);
+ break;
+ }
+ }
+}
diff --git a/libide/ide-configuration-manager.h b/libide/ide-configuration-manager.h
new file mode 100644
index 0000000..efe8dbc
--- /dev/null
+++ b/libide/ide-configuration-manager.h
@@ -0,0 +1,53 @@
+/* ide-configuration-manager.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_CONFIGURATION_MANAGER_H
+#define IDE_CONFIGURATION_MANAGER_H
+
+#include <gio/gio.h>
+
+#include "ide-object.h"
+#include "ide-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CONFIGURATION_MANAGER (ide_configuration_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeConfigurationManager, ide_configuration_manager, IDE, CONFIGURATION_MANAGER,
IdeObject)
+
+IdeConfiguration *ide_configuration_manager_get_current (IdeConfigurationManager *self);
+void ide_configuration_manager_set_current (IdeConfigurationManager *self,
+ IdeConfiguration *configuration);
+IdeConfiguration *ide_configuration_manager_get_configuration (IdeConfigurationManager *self,
+ const gchar *id);
+void ide_configuration_manager_add (IdeConfigurationManager *self,
+ IdeConfiguration *configuration);
+void ide_configuration_manager_remove (IdeConfigurationManager *self,
+ IdeConfiguration *configuration);
+void ide_configuration_manager_save_async (IdeConfigurationManager *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_configuration_manager_save_finish (IdeConfigurationManager *self,
+ GAsyncResult *result,
+ GError **error);
+
+
+G_END_DECLS
+
+#endif /* IDE_CONFIGURATION_MANAGER_H */
diff --git a/libide/ide-configuration.c b/libide/ide-configuration.c
new file mode 100644
index 0000000..e6d9fcf
--- /dev/null
+++ b/libide/ide-configuration.c
@@ -0,0 +1,824 @@
+/* ide-configuration.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-configuration"
+
+#include <string.h>
+
+#include "ide-configuration.h"
+#include "ide-context.h"
+#include "ide-device.h"
+#include "ide-device-manager.h"
+#include "ide-environment.h"
+#include "ide-runtime.h"
+#include "ide-runtime-manager.h"
+
+struct _IdeConfiguration
+{
+ IdeObject parent_instance;
+
+ gchar *config_opts;
+ gchar *device_id;
+ gchar *display_name;
+ gchar *id;
+ gchar *prefix;
+ gchar *runtime_id;
+
+ IdeEnvironment *environment;
+
+ gint parallelism;
+
+ guint dirty : 1;
+ guint debug : 1;
+};
+
+G_DEFINE_TYPE (IdeConfiguration, ide_configuration, IDE_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_CONFIG_OPTS,
+ PROP_DEBUG,
+ PROP_DEVICE,
+ PROP_DEVICE_ID,
+ PROP_DIRTY,
+ PROP_DISPLAY_NAME,
+ PROP_ENVIRON,
+ PROP_ID,
+ PROP_PARALLELISM,
+ PROP_PREFIX,
+ PROP_RUNTIME,
+ PROP_RUNTIME_ID,
+ N_PROPS
+};
+
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [LAST_SIGNAL];
+
+static void
+ide_configuration_set_id (IdeConfiguration *self,
+ const gchar *id)
+{
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+ g_return_if_fail (id != NULL);
+
+ if (g_strcmp0 (id, self->id) != 0)
+ {
+ g_free (self->id);
+ self->id = g_strdup (id);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ID]);
+ }
+}
+
+static void
+ide_configuration_device_manager_items_changed (IdeConfiguration *self,
+ guint position,
+ guint added,
+ guint removed,
+ IdeDeviceManager *device_manager)
+{
+ IdeDevice *device;
+
+ g_assert (IDE_IS_CONFIGURATION (self));
+ g_assert (IDE_IS_DEVICE_MANAGER (device_manager));
+
+ device = ide_device_manager_get_device (device_manager, self->device_id);
+
+ if (device != NULL)
+ ide_device_prepare_configuration (device, self);
+}
+
+static void
+ide_configuration_runtime_manager_items_changed (IdeConfiguration *self,
+ guint position,
+ guint added,
+ guint removed,
+ IdeRuntimeManager *runtime_manager)
+{
+ IdeRuntime *runtime;
+
+ g_assert (IDE_IS_CONFIGURATION (self));
+ g_assert (IDE_IS_RUNTIME_MANAGER (runtime_manager));
+
+ runtime = ide_runtime_manager_get_runtime (runtime_manager, self->runtime_id);
+
+ if (runtime != NULL)
+ ide_runtime_prepare_configuration (runtime, self);
+}
+
+static void
+ide_configuration_environment_changed (IdeConfiguration *self,
+ guint position,
+ guint added,
+ guint removed,
+ IdeEnvironment *environment)
+{
+ g_assert (IDE_IS_CONFIGURATION (self));
+ g_assert (IDE_IS_ENVIRONMENT (environment));
+
+ ide_configuration_set_dirty (self, TRUE);
+}
+
+static void
+ide_configuration_constructed (GObject *object)
+{
+ IdeConfiguration *self = (IdeConfiguration *)object;
+ IdeContext *context;
+ IdeDeviceManager *device_manager;
+ IdeRuntimeManager *runtime_manager;
+
+ G_OBJECT_CLASS (ide_configuration_parent_class)->constructed (object);
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ device_manager = ide_context_get_device_manager (context);
+ runtime_manager = ide_context_get_runtime_manager (context);
+
+ g_signal_connect_object (device_manager,
+ "items-changed",
+ G_CALLBACK (ide_configuration_device_manager_items_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (runtime_manager,
+ "items-changed",
+ G_CALLBACK (ide_configuration_runtime_manager_items_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ ide_configuration_device_manager_items_changed (self, 0, 0, 0, device_manager);
+ ide_configuration_runtime_manager_items_changed (self, 0, 0, 0, runtime_manager);
+}
+
+static void
+ide_configuration_finalize (GObject *object)
+{
+ IdeConfiguration *self = (IdeConfiguration *)object;
+
+ g_clear_object (&self->environment);
+
+ g_clear_pointer (&self->config_opts, g_free);
+ g_clear_pointer (&self->device_id, g_free);
+ g_clear_pointer (&self->display_name, g_free);
+ g_clear_pointer (&self->id, g_free);
+ g_clear_pointer (&self->prefix, g_free);
+ g_clear_pointer (&self->runtime_id, g_free);
+
+ G_OBJECT_CLASS (ide_configuration_parent_class)->finalize (object);
+}
+
+static void
+ide_configuration_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeConfiguration *self = IDE_CONFIGURATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIG_OPTS:
+ g_value_set_string (value, ide_configuration_get_config_opts (self));
+ break;
+
+ case PROP_DEBUG:
+ g_value_set_boolean (value, ide_configuration_get_debug (self));
+ break;
+
+ case PROP_DEVICE:
+ g_value_set_object (value, ide_configuration_get_device (self));
+ break;
+
+ case PROP_DIRTY:
+ g_value_set_boolean (value, ide_configuration_get_dirty (self));
+ break;
+
+ case PROP_DISPLAY_NAME:
+ g_value_set_string (value, ide_configuration_get_display_name (self));
+ break;
+
+ case PROP_ENVIRON:
+ g_value_set_boxed (value, ide_configuration_get_environ (self));
+ break;
+
+ case PROP_ID:
+ g_value_set_string (value, ide_configuration_get_id (self));
+ break;
+
+ case PROP_PARALLELISM:
+ g_value_set_int (value, ide_configuration_get_parallelism (self));
+ break;
+
+ case PROP_PREFIX:
+ g_value_set_string (value, ide_configuration_get_prefix (self));
+ break;
+
+ case PROP_RUNTIME:
+ g_value_set_object (value, ide_configuration_get_runtime (self));
+ break;
+
+ case PROP_RUNTIME_ID:
+ g_value_set_object (value, ide_configuration_get_runtime (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_configuration_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeConfiguration *self = IDE_CONFIGURATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIG_OPTS:
+ ide_configuration_set_config_opts (self, g_value_get_string (value));
+ break;
+
+ case PROP_DEBUG:
+ ide_configuration_set_debug (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_DEVICE:
+ ide_configuration_set_device (self, g_value_get_object (value));
+ break;
+
+ case PROP_DEVICE_ID:
+ ide_configuration_set_device_id (self, g_value_get_string (value));
+ break;
+
+ case PROP_DIRTY:
+ ide_configuration_set_dirty (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_DISPLAY_NAME:
+ ide_configuration_set_display_name (self, g_value_get_string (value));
+ break;
+
+ case PROP_ID:
+ ide_configuration_set_id (self, g_value_get_string (value));
+ break;
+
+ case PROP_PREFIX:
+ ide_configuration_set_prefix (self, g_value_get_string (value));
+ break;
+
+ case PROP_PARALLELISM:
+ ide_configuration_set_parallelism (self, g_value_get_int (value));
+ break;
+
+ case PROP_RUNTIME:
+ ide_configuration_set_runtime (self, g_value_get_object (value));
+ break;
+
+ case PROP_RUNTIME_ID:
+ ide_configuration_set_runtime_id (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_configuration_class_init (IdeConfigurationClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = ide_configuration_constructed;
+ object_class->finalize = ide_configuration_finalize;
+ object_class->get_property = ide_configuration_get_property;
+ object_class->set_property = ide_configuration_set_property;
+
+ properties [PROP_CONFIG_OPTS] =
+ g_param_spec_string ("config-opts",
+ "Config Options",
+ "Parameters to bootstrap the project",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DEBUG] =
+ g_param_spec_boolean ("debug",
+ "Debug",
+ "Debug",
+ TRUE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DEVICE] =
+ g_param_spec_object ("device",
+ "Device",
+ "Device",
+ IDE_TYPE_DEVICE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DEVICE_ID] =
+ g_param_spec_string ("device-id",
+ "Device Id",
+ "The identifier of the device",
+ "local",
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DIRTY] =
+ g_param_spec_boolean ("dirty",
+ "Dirty",
+ "If the configuration has been changed.",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DISPLAY_NAME] =
+ g_param_spec_string ("display-name",
+ "Display Name",
+ "Display Name",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ENVIRON] =
+ g_param_spec_boxed ("environ",
+ "Environ",
+ "Environ",
+ G_TYPE_STRV,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ID] =
+ g_param_spec_string ("id",
+ "Id",
+ "Id",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PARALLELISM] =
+ g_param_spec_int ("parallelism",
+ "Parallelism",
+ "Parallelism",
+ -1,
+ G_MAXINT,
+ -1,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PREFIX] =
+ g_param_spec_string ("prefix",
+ "Prefix",
+ "Prefix",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_RUNTIME] =
+ g_param_spec_object ("runtime",
+ "Runtime",
+ "Runtime",
+ IDE_TYPE_RUNTIME,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_RUNTIME_ID] =
+ g_param_spec_string ("runtime-id",
+ "Runtime Id",
+ "The identifier of the runtime",
+ "host",
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ signals [CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+}
+
+static void
+ide_configuration_init (IdeConfiguration *self)
+{
+ self->device_id = g_strdup ("local");
+ self->runtime_id = g_strdup ("host");
+ self->debug = TRUE;
+ self->environment = ide_environment_new ();
+
+ g_signal_connect_object (self->environment,
+ "items-changed",
+ G_CALLBACK (ide_configuration_environment_changed),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+IdeConfiguration *
+ide_configuration_new (IdeContext *context,
+ const gchar *id,
+ const gchar *device_id,
+ const gchar *runtime_id)
+{
+ g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (device_id != NULL, NULL);
+ g_return_val_if_fail (runtime_id != NULL, NULL);
+
+ return g_object_new (IDE_TYPE_CONFIGURATION,
+ "context", context,
+ "device-id", device_id,
+ "id", id,
+ "runtime-id", runtime_id,
+ NULL);
+}
+
+const gchar *
+ide_configuration_get_device_id (IdeConfiguration *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+ return self->device_id;
+}
+
+void
+ide_configuration_set_device_id (IdeConfiguration *self,
+ const gchar *device_id)
+{
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+ g_return_if_fail (device_id != NULL);
+
+ if (g_strcmp0 (device_id, self->device_id) != 0)
+ {
+ IdeContext *context;
+ IdeDeviceManager *device_manager;
+
+ g_free (self->device_id);
+ self->device_id = g_strdup (device_id);
+
+ ide_configuration_set_dirty (self, TRUE);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEVICE_ID]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEVICE]);
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ device_manager = ide_context_get_device_manager (context);
+ ide_configuration_device_manager_items_changed (self, 0, 0, 0, device_manager);
+ }
+}
+
+/**
+ * ide_configuration_get_device:
+ * @self: An #IdeConfiguration
+ *
+ * Gets the device for the configuration.
+ *
+ * Returns: (transfer none) (nullable): An #IdeDevice.
+ */
+IdeDevice *
+ide_configuration_get_device (IdeConfiguration *self)
+{
+ IdeDeviceManager *device_manager;
+ IdeContext *context;
+
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ device_manager = ide_context_get_device_manager (context);
+
+ return ide_device_manager_get_device (device_manager, self->device_id);
+}
+
+void
+ide_configuration_set_device (IdeConfiguration *self,
+ IdeDevice *device)
+{
+ const gchar *device_id = "local";
+
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+ g_return_if_fail (!device || IDE_IS_DEVICE (device));
+
+ if (device != NULL)
+ device_id = ide_device_get_id (device);
+
+ ide_configuration_set_device_id (self, device_id);
+}
+
+const gchar *
+ide_configuration_get_runtime_id (IdeConfiguration *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+ return self->runtime_id;
+}
+
+void
+ide_configuration_set_runtime_id (IdeConfiguration *self,
+ const gchar *runtime_id)
+{
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+ g_return_if_fail (runtime_id != NULL);
+
+ if (g_strcmp0 (runtime_id, self->runtime_id) != 0)
+ {
+ IdeRuntimeManager *runtime_manager;
+ IdeContext *context;
+
+ g_free (self->runtime_id);
+ self->runtime_id = g_strdup (runtime_id);
+
+ ide_configuration_set_dirty (self, TRUE);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUNTIME_ID]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUNTIME]);
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ runtime_manager = ide_context_get_runtime_manager (context);
+ ide_configuration_runtime_manager_items_changed (self, 0, 0, 0, runtime_manager);
+ }
+}
+
+/**
+ * ide_configuration_get_runtime:
+ * @self: An #IdeConfiguration
+ *
+ * Gets the runtime for the configuration.
+ *
+ * Returns: (transfer none) (nullable): An #IdeRuntime
+ */
+IdeRuntime *
+ide_configuration_get_runtime (IdeConfiguration *self)
+{
+ IdeRuntimeManager *runtime_manager;
+ IdeContext *context;
+
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ runtime_manager = ide_context_get_runtime_manager (context);
+
+ return ide_runtime_manager_get_runtime (runtime_manager, self->runtime_id);
+}
+
+void
+ide_configuration_set_runtime (IdeConfiguration *self,
+ IdeRuntime *runtime)
+{
+ const gchar *runtime_id = "host";
+
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+ g_return_if_fail (!runtime || IDE_IS_RUNTIME (runtime));
+
+ if (runtime != NULL)
+ runtime_id = ide_runtime_get_id (runtime);
+
+ ide_configuration_set_runtime_id (self, runtime_id);
+}
+
+/**
+ * ide_configuration_get_environ:
+ * @self: An #IdeConfiguration
+ *
+ * Gets the environment to use when spawning processes.
+ *
+ * Returns: (transfer full): An array of key=value environment variables.
+ */
+gchar **
+ide_configuration_get_environ (IdeConfiguration *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+ return ide_environment_get_environ (self->environment);
+}
+
+const gchar *
+ide_configuration_getenv (IdeConfiguration *self,
+ const gchar *key)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+
+ return ide_environment_getenv (self->environment, key);
+}
+
+void
+ide_configuration_setenv (IdeConfiguration *self,
+ const gchar *key,
+ const gchar *value)
+{
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+ g_return_if_fail (key != NULL);
+
+ ide_environment_setenv (self->environment, key, value);
+}
+
+const gchar *
+ide_configuration_get_id (IdeConfiguration *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+ return self->id;
+}
+
+const gchar *
+ide_configuration_get_prefix (IdeConfiguration *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+ return self->prefix;
+}
+
+void
+ide_configuration_set_prefix (IdeConfiguration *self,
+ const gchar *prefix)
+{
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+
+ if (g_strcmp0 (prefix, self->prefix) != 0)
+ {
+ g_free (self->prefix);
+ self->prefix = g_strdup (prefix);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PREFIX]);
+ ide_configuration_set_dirty (self, TRUE);
+ }
+}
+
+gint
+ide_configuration_get_parallelism (IdeConfiguration *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), -1);
+
+ return self->parallelism;
+}
+
+void
+ide_configuration_set_parallelism (IdeConfiguration *self,
+ gint parallelism)
+{
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+ g_return_if_fail (parallelism >= -1);
+
+ if (parallelism != self->parallelism)
+ {
+ self->parallelism = parallelism;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PARALLELISM]);
+ }
+}
+
+gboolean
+ide_configuration_get_debug (IdeConfiguration *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), FALSE);
+
+ return self->debug;
+}
+
+void
+ide_configuration_set_debug (IdeConfiguration *self,
+ gboolean debug)
+{
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+
+ debug = !!debug;
+
+ if (debug != self->debug)
+ {
+ self->debug = debug;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEBUG]);
+ ide_configuration_set_dirty (self, TRUE);
+ }
+}
+
+const gchar *
+ide_configuration_get_display_name (IdeConfiguration *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+ return self->display_name;
+}
+
+void
+ide_configuration_set_display_name (IdeConfiguration *self,
+ const gchar *display_name)
+{
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+
+ if (g_strcmp0 (display_name, self->display_name) != 0)
+ {
+ g_free (self->display_name);
+ self->display_name = g_strdup (display_name);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISPLAY_NAME]);
+ }
+}
+
+gboolean
+ide_configuration_get_dirty (IdeConfiguration *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), FALSE);
+
+ return self->dirty;
+}
+
+void
+ide_configuration_set_dirty (IdeConfiguration *self,
+ gboolean dirty)
+{
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+
+ dirty = !!dirty;
+
+ if (dirty != self->dirty)
+ {
+ self->dirty = dirty;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DIRTY]);
+ }
+
+ /*
+ * Always emit the changed signal so that the configuration manager
+ * can queue a writeback of the configuration.
+ */
+ g_signal_emit (self, signals [CHANGED], 0);
+}
+
+/**
+ * ide_configuration_get_environment:
+ *
+ * Returns: (transfer none): An #IdeEnvironment.
+ */
+IdeEnvironment *
+ide_configuration_get_environment (IdeConfiguration *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+ return self->environment;
+}
+
+const gchar *
+ide_configuration_get_config_opts (IdeConfiguration *self)
+{
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+ return self->config_opts;
+}
+
+void
+ide_configuration_set_config_opts (IdeConfiguration *self,
+ const gchar *config_opts)
+{
+ g_return_if_fail (IDE_IS_CONFIGURATION (self));
+
+ if (g_strcmp0 (config_opts, self->config_opts) != 0)
+ {
+ g_free (self->config_opts);
+ self->config_opts = g_strdup (config_opts);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONFIG_OPTS]);
+ ide_configuration_set_dirty (self, TRUE);
+ }
+}
+
+/**
+ * ide_configuration_duplicate:
+ * @self: An #IdeConfiguration
+ *
+ * Copies the configuration into a new configuration.
+ *
+ * Returns: (transfer full): An #IdeConfiguration.
+ */
+IdeConfiguration *
+ide_configuration_duplicate (IdeConfiguration *self)
+{
+ static gint next_counter = 2;
+ IdeConfiguration *copy;
+ IdeContext *context;
+ g_autofree gchar *id = NULL;
+ g_autofree gchar *name = NULL;
+
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ id = g_strdup_printf ("%s %d", self->id, next_counter++);
+ name = g_strdup_printf ("%s Copy", self->display_name);
+
+ copy = g_object_new (IDE_TYPE_CONFIGURATION,
+ "config-opts", self->config_opts,
+ "context", context,
+ "device-id", self->device_id,
+ "display-name", name,
+ "id", id,
+ "prefix", self->prefix,
+ "runtime-id", self->runtime_id,
+ NULL);
+
+ copy->environment = ide_environment_copy (self->environment);
+
+ return copy;
+}
diff --git a/libide/ide-configuration.h b/libide/ide-configuration.h
new file mode 100644
index 0000000..e4d0596
--- /dev/null
+++ b/libide/ide-configuration.h
@@ -0,0 +1,79 @@
+/* ide-configuration.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_CONFIGURATION_H
+#define IDE_CONFIGURATION_H
+
+#include <gio/gio.h>
+
+#include "ide-object.h"
+#include "ide-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CONFIGURATION (ide_configuration_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeConfiguration, ide_configuration, IDE, CONFIGURATION, IdeObject)
+
+IdeConfiguration *ide_configuration_new (IdeContext *context,
+ const gchar *id,
+ const gchar *device_id,
+ const gchar *runtime_id);
+const gchar *ide_configuration_get_id (IdeConfiguration *self);
+const gchar *ide_configuration_get_runtime_id (IdeConfiguration *self);
+void ide_configuration_set_runtime_id (IdeConfiguration *self,
+ const gchar *runtime_id);
+const gchar *ide_configuration_get_device_id (IdeConfiguration *self);
+void ide_configuration_set_device_id (IdeConfiguration *self,
+ const gchar *device_id);
+IdeDevice *ide_configuration_get_device (IdeConfiguration *self);
+void ide_configuration_set_device (IdeConfiguration *self,
+ IdeDevice *device);
+gboolean ide_configuration_get_dirty (IdeConfiguration *self);
+void ide_configuration_set_dirty (IdeConfiguration *self,
+ gboolean dirty);
+const gchar *ide_configuration_get_display_name (IdeConfiguration *self);
+void ide_configuration_set_display_name (IdeConfiguration *self,
+ const gchar *display_name);
+IdeRuntime *ide_configuration_get_runtime (IdeConfiguration *self);
+void ide_configuration_set_runtime (IdeConfiguration *self,
+ IdeRuntime *runtime);
+gchar **ide_configuration_get_environ (IdeConfiguration *self);
+const gchar *ide_configuration_getenv (IdeConfiguration *self,
+ const gchar *key);
+void ide_configuration_setenv (IdeConfiguration *self,
+ const gchar *key,
+ const gchar *value);
+gboolean ide_configuration_get_debug (IdeConfiguration *self);
+void ide_configuration_set_debug (IdeConfiguration *self,
+ gboolean debug);
+const gchar *ide_configuration_get_prefix (IdeConfiguration *self);
+void ide_configuration_set_prefix (IdeConfiguration *self,
+ const gchar *prefix);
+const gchar *ide_configuration_get_config_opts (IdeConfiguration *self);
+void ide_configuration_set_config_opts (IdeConfiguration *self,
+ const gchar *config_opts);
+gint ide_configuration_get_parallelism (IdeConfiguration *self);
+void ide_configuration_set_parallelism (IdeConfiguration *self,
+ gint parallelism);
+IdeEnvironment *ide_configuration_get_environment (IdeConfiguration *self);
+IdeConfiguration *ide_configuration_duplicate (IdeConfiguration *self);
+
+G_END_DECLS
+
+#endif /* IDE_CONFIGURATION_H */
diff --git a/libide/ide-context.c b/libide/ide-context.c
index 5196892..939eedb 100644
--- a/libide/ide-context.c
+++ b/libide/ide-context.c
@@ -27,6 +27,7 @@
#include "ide-buffer-manager.h"
#include "ide-buffer.h"
#include "ide-build-system.h"
+#include "ide-configuration-manager.h"
#include "ide-context.h"
#include "ide-debug.h"
#include "ide-device-manager.h"
@@ -35,6 +36,7 @@
#include "ide-project.h"
#include "ide-project-item.h"
#include "ide-project-files.h"
+#include "ide-runtime-manager.h"
#include "ide-script-manager.h"
#include "ide-search-engine.h"
#include "ide-search-provider.h"
@@ -57,9 +59,11 @@ struct _IdeContext
IdeBackForwardList *back_forward_list;
IdeBufferManager *buffer_manager;
IdeBuildSystem *build_system;
+ IdeConfigurationManager *configuration_manager;
IdeDeviceManager *device_manager;
IdeDoap *doap;
GtkRecentManager *recent_manager;
+ IdeRuntimeManager *runtime_manager;
IdeScriptManager *script_manager;
IdeSearchEngine *search_engine;
IdeSourceSnippetsManager *snippets_manager;
@@ -91,10 +95,12 @@ enum {
PROP_BACK_FORWARD_LIST,
PROP_BUFFER_MANAGER,
PROP_BUILD_SYSTEM,
+ PROP_CONFIGURATION_MANAGER,
PROP_DEVICE_MANAGER,
PROP_PROJECT_FILE,
PROP_PROJECT,
PROP_ROOT_BUILD_DIR,
+ PROP_RUNTIME_MANAGER,
PROP_SCRIPT_MANAGER,
PROP_SEARCH_ENGINE,
PROP_SNIPPETS_MANAGER,
@@ -531,11 +537,13 @@ ide_context_finalize (GObject *object)
g_clear_pointer (&self->recent_projects_path, g_free);
g_clear_object (&self->build_system);
+ g_clear_object (&self->configuration_manager);
g_clear_object (&self->device_manager);
g_clear_object (&self->doap);
g_clear_object (&self->project);
g_clear_object (&self->project_file);
g_clear_object (&self->recent_manager);
+ g_clear_object (&self->runtime_manager);
g_clear_object (&self->unsaved_files);
g_clear_object (&self->vcs);
@@ -570,6 +578,10 @@ ide_context_get_property (GObject *object,
g_value_set_object (value, ide_context_get_build_system (self));
break;
+ case PROP_CONFIGURATION_MANAGER:
+ g_value_set_object (value, ide_context_get_configuration_manager (self));
+ break;
+
case PROP_DEVICE_MANAGER:
g_value_set_object (value, ide_context_get_device_manager (self));
break;
@@ -586,6 +598,10 @@ ide_context_get_property (GObject *object,
g_value_set_string (value, ide_context_get_root_build_dir (self));
break;
+ case PROP_RUNTIME_MANAGER:
+ g_value_set_object (value, ide_context_get_runtime_manager (self));
+ break;
+
case PROP_SCRIPT_MANAGER:
g_value_set_object (value, ide_context_get_script_manager (self));
break;
@@ -665,6 +681,13 @@ ide_context_class_init (IdeContextClass *klass)
IDE_TYPE_BUILD_SYSTEM,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ properties [PROP_CONFIGURATION_MANAGER] =
+ g_param_spec_object ("configuration-manager",
+ "Configuration Manager",
+ "The configuration manager for the context",
+ IDE_TYPE_CONFIGURATION_MANAGER,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
properties [PROP_DEVICE_MANAGER] =
g_param_spec_object ("device-manager",
"Device Manager",
@@ -695,6 +718,13 @@ ide_context_class_init (IdeContextClass *klass)
NULL,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ properties [PROP_RUNTIME_MANAGER] =
+ g_param_spec_object ("runtime-manager",
+ "Runtime Manager",
+ "Runtime Manager",
+ IDE_TYPE_RUNTIME_MANAGER,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
properties [PROP_SCRIPT_MANAGER] =
g_param_spec_object ("script-manager",
"Script Manager",
@@ -782,10 +812,18 @@ ide_context_init (IdeContext *self)
"context", self,
NULL);
+ self->configuration_manager = g_object_new (IDE_TYPE_CONFIGURATION_MANAGER,
+ "context", self,
+ NULL);
+
self->project = g_object_new (IDE_TYPE_PROJECT,
"context", self,
NULL);
+ self->runtime_manager = g_object_new (IDE_TYPE_RUNTIME_MANAGER,
+ "context", self,
+ NULL);
+
self->unsaved_files = g_object_new (IDE_TYPE_UNSAVED_FILES,
"context", self,
NULL);
@@ -1338,6 +1376,44 @@ ide_context_init_search_engine (gpointer source_object,
}
static void
+ide_context_init_configuration_manager_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GAsyncInitable *initable = (GAsyncInitable *)object;
+ g_autoptr(GTask) task = user_data;
+ GError *error = NULL;
+
+ g_assert (G_IS_ASYNC_INITABLE (initable));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ if (!g_async_initable_init_finish (initable, result, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_context_init_configuration_manager (gpointer source_object,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdeContext *self = source_object;
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (IDE_IS_CONTEXT (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_async_initable_init_async (G_ASYNC_INITABLE (self->configuration_manager),
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ ide_context_init_configuration_manager_cb,
+ g_object_ref (task));
+}
+
+static void
ide_context_init_loaded (gpointer source_object,
GCancellable *cancellable,
GAsyncReadyCallback callback,
@@ -1381,6 +1457,7 @@ ide_context_init_async (GAsyncInitable *initable,
ide_context_init_unsaved_files,
ide_context_init_add_recent,
ide_context_init_search_engine,
+ ide_context_init_configuration_manager,
ide_context_init_loaded,
NULL);
}
@@ -1501,6 +1578,50 @@ ide_context_unload_buffer_manager (gpointer source_object,
}
static void
+ide_context_unload__configuration_manager_save_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeConfigurationManager *manager = (IdeConfigurationManager *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
+ g_assert (G_IS_TASK (task));
+
+ /* unfortunate if this happens, but not much we can do */
+ if (!ide_configuration_manager_save_finish (manager, result, &error))
+ g_warning ("%s", error->message);
+
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_context_unload_configuration_manager (gpointer source_object,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdeContext *self = source_object;
+ g_autoptr(GTask) task = NULL;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_CONTEXT (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (self->configuration_manager));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ ide_configuration_manager_save_async (self->configuration_manager,
+ cancellable,
+ ide_context_unload__configuration_manager_save_cb,
+ g_object_ref (task));
+
+ IDE_EXIT;
+}
+
+static void
ide_context_unload__back_forward_list_save_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
@@ -1618,10 +1739,14 @@ ide_context_do_unload_locked (IdeContext *self)
task = self->delayed_unload_task;
self->delayed_unload_task = NULL;
+ g_clear_object (&self->device_manager);
+ g_clear_object (&self->runtime_manager);
+
ide_async_helper_run (self,
g_task_get_cancellable (task),
ide_context_unload_cb,
g_object_ref (task),
+ ide_context_unload_configuration_manager,
ide_context_unload_back_forward_list,
ide_context_unload_buffer_manager,
ide_context_unload_unsaved_files,
@@ -1915,3 +2040,63 @@ ide_context_release (IdeContext *self)
g_object_unref (self);
}
+
+/**
+ * ide_context_get_runtime_manager:
+ * @self: An #IdeContext
+ *
+ * Gets the #IdeRuntimeManager for the LibIDE context.
+ *
+ * The runtime manager provies access to #IdeRuntime instances via the
+ * #GListModel interface. These can provide support for building projects
+ * in various runtimes such as xdg-app.
+ *
+ * Returns: (transfer none): An #IdeRuntimeManager.
+ */
+IdeRuntimeManager *
+ide_context_get_runtime_manager (IdeContext *self)
+{
+ g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
+
+ return self->runtime_manager;
+}
+
+/**
+ * ide_context_get_configuration_manager:
+ * @self: An #IdeContext
+ *
+ * Gets the #IdeConfigurationManager for the context.
+ *
+ * The configuration manager is responsible for loading and saving
+ * configurations. Configurations consist of information about how to
+ * perform a particular build. Such information includes the target
+ * #IdeDevice, the #IdeRuntime to use, and various other build options.
+ *
+ * Returns: (transfer none): An #IdeConfigurationManager.
+ */
+IdeConfigurationManager *
+ide_context_get_configuration_manager (IdeContext *self)
+{
+ g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
+
+ return self->configuration_manager;
+}
+
+void
+ide_context_warning (IdeContext *self,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+
+ g_return_if_fail (IDE_IS_CONTEXT (self));
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ /*
+ * TODO: Track logging information so that we can display warnings
+ * to the user in the workbench.
+ */
+ g_logv ("Ide", G_LOG_LEVEL_WARNING, format, args);
+ va_end (args);
+}
diff --git a/libide/ide-context.h b/libide/ide-context.h
index ef249ee..31d9158 100644
--- a/libide/ide-context.h
+++ b/libide/ide-context.h
@@ -30,50 +30,55 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (IdeContext, ide_context, IDE, CONTEXT, GObject)
-IdeBackForwardList *ide_context_get_back_forward_list (IdeContext *self);
-GFile *ide_context_get_project_file (IdeContext *self);
-IdeBufferManager *ide_context_get_buffer_manager (IdeContext *self);
-IdeBuildSystem *ide_context_get_build_system (IdeContext *self);
-IdeDeviceManager *ide_context_get_device_manager (IdeContext *self);
-IdeProject *ide_context_get_project (IdeContext *self);
-GtkRecentManager *ide_context_get_recent_manager (IdeContext *self);
-IdeScriptManager *ide_context_get_script_manager (IdeContext *self);
-IdeSearchEngine *ide_context_get_search_engine (IdeContext *self);
-IdeSettings *ide_context_get_settings (IdeContext *self,
- const gchar *schema_id,
- const gchar *relative_path);
-IdeSourceSnippetsManager *ide_context_get_snippets_manager (IdeContext *self);
-IdeUnsavedFiles *ide_context_get_unsaved_files (IdeContext *self);
-IdeVcs *ide_context_get_vcs (IdeContext *self);
-const gchar *ide_context_get_root_build_dir (IdeContext *self);
-gpointer ide_context_get_service_typed (IdeContext *self,
- GType service_type);
-void ide_context_unload_async (IdeContext *self,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean ide_context_unload_finish (IdeContext *self,
- GAsyncResult *result,
- GError **error);
-void ide_context_new_async (GFile *project_file,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-IdeContext *ide_context_new_finish (GAsyncResult *result,
- GError **error);
-void ide_context_set_root_build_dir (IdeContext *self,
- const gchar *root_build_dir);
-void ide_context_restore_async (IdeContext *self,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean ide_context_restore_finish (IdeContext *self,
- GAsyncResult *result,
- GError **error);
-void ide_context_hold (IdeContext *self);
-void ide_context_hold_for_object (IdeContext *self,
- gpointer instance);
-void ide_context_release (IdeContext *self);
+IdeBackForwardList *ide_context_get_back_forward_list (IdeContext *self);
+GFile *ide_context_get_project_file (IdeContext *self);
+IdeBufferManager *ide_context_get_buffer_manager (IdeContext *self);
+IdeBuildSystem *ide_context_get_build_system (IdeContext *self);
+IdeConfigurationManager *ide_context_get_configuration_manager (IdeContext *self);
+IdeDeviceManager *ide_context_get_device_manager (IdeContext *self);
+IdeProject *ide_context_get_project (IdeContext *self);
+GtkRecentManager *ide_context_get_recent_manager (IdeContext *self);
+IdeRuntimeManager *ide_context_get_runtime_manager (IdeContext *self);
+IdeScriptManager *ide_context_get_script_manager (IdeContext *self);
+IdeSearchEngine *ide_context_get_search_engine (IdeContext *self);
+IdeSettings *ide_context_get_settings (IdeContext *self,
+ const gchar *schema_id,
+ const gchar *relative_path);
+IdeSourceSnippetsManager *ide_context_get_snippets_manager (IdeContext *self);
+IdeUnsavedFiles *ide_context_get_unsaved_files (IdeContext *self);
+IdeVcs *ide_context_get_vcs (IdeContext *self);
+const gchar *ide_context_get_root_build_dir (IdeContext *self);
+gpointer ide_context_get_service_typed (IdeContext *self,
+ GType service_type);
+void ide_context_unload_async (IdeContext *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_context_unload_finish (IdeContext *self,
+ GAsyncResult *result,
+ GError **error);
+void ide_context_new_async (GFile *project_file,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IdeContext *ide_context_new_finish (GAsyncResult *result,
+ GError **error);
+void ide_context_set_root_build_dir (IdeContext *self,
+ const gchar *root_build_dir);
+void ide_context_restore_async (IdeContext *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_context_restore_finish (IdeContext *self,
+ GAsyncResult *result,
+ GError **error);
+void ide_context_hold (IdeContext *self);
+void ide_context_hold_for_object (IdeContext *self,
+ gpointer instance);
+void ide_context_release (IdeContext *self);
+void ide_context_warning (IdeContext *self,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
G_END_DECLS
diff --git a/libide/ide-device-manager.c b/libide/ide-device-manager.c
index 58a952b..8df4f56 100644
--- a/libide/ide-device-manager.c
+++ b/libide/ide-device-manager.c
@@ -128,6 +128,9 @@ ide_device_manager__provider_device_removed (IdeDeviceManager *self,
g_return_if_fail (IDE_IS_DEVICE (device));
g_return_if_fail (IDE_IS_DEVICE_PROVIDER (provider));
+ if (self->devices == NULL)
+ return;
+
for (i = 0; i < self->devices->len; i++)
{
IdeDevice *current = g_ptr_array_index (self->devices, i);
diff --git a/libide/ide-device.c b/libide/ide-device.c
index 897a658..13d67be 100644
--- a/libide/ide-device.c
+++ b/libide/ide-device.c
@@ -18,6 +18,7 @@
#include <glib/gi18n.h>
+#include "ide-configuration.h"
#include "ide-device.h"
typedef struct
@@ -39,27 +40,6 @@ enum {
static GParamSpec *properties [LAST_PROP];
/**
- * ide_device_get_config:
- * @device: A #IdeDevice.
- *
- * Retrieves any custom configuration that is required to build for the
- * device. Such values might include additional options to autoconf
- * or paths to cross-compilers.
- *
- * Returns: (transfer none) (nullable): A #GKeyFile or %NULL.
- */
-GKeyFile *
-ide_device_get_config (IdeDevice *device)
-{
- g_return_val_if_fail (IDE_IS_DEVICE (device), NULL);
-
- if (IDE_DEVICE_GET_CLASS (device)->get_config)
- return IDE_DEVICE_GET_CLASS (device)->get_config (device);
-
- return NULL;
-}
-
-/**
* ide_device_get_display_name:
*
* This function returns the name of the device. If no name has been set, then
@@ -257,3 +237,14 @@ static void
ide_device_init (IdeDevice *self)
{
}
+
+void
+ide_device_prepare_configuration (IdeDevice *self,
+ IdeConfiguration *configuration)
+{
+ g_assert (IDE_IS_DEVICE (self));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ if (IDE_DEVICE_GET_CLASS (self)->prepare_configuration)
+ IDE_DEVICE_GET_CLASS (self)->prepare_configuration (self, configuration);
+}
diff --git a/libide/ide-device.h b/libide/ide-device.h
index f9bfd11..595436c 100644
--- a/libide/ide-device.h
+++ b/libide/ide-device.h
@@ -20,6 +20,7 @@
#define IDE_DEVICE_H
#include "ide-object.h"
+#include "ide-types.h"
G_BEGIN_DECLS
@@ -31,18 +32,20 @@ struct _IdeDeviceClass
{
IdeObjectClass parent;
- GKeyFile *(*get_config) (IdeDevice *device);
- const gchar *(*get_system_type) (IdeDevice *device);
+ const gchar *(*get_system_type) (IdeDevice *self);
+ void (*prepare_configuration) (IdeDevice *self,
+ IdeConfiguration *configuration);
};
-GKeyFile *ide_device_get_config (IdeDevice *device);
-const gchar *ide_device_get_display_name (IdeDevice *device);
-void ide_device_set_display_name (IdeDevice *device,
- const gchar *display_name);
-const gchar *ide_device_get_id (IdeDevice *device);
-void ide_device_set_id (IdeDevice *device,
- const gchar *id);
-const gchar *ide_device_get_system_type (IdeDevice *device);
+const gchar *ide_device_get_display_name (IdeDevice *self);
+void ide_device_set_display_name (IdeDevice *self,
+ const gchar *display_name);
+const gchar *ide_device_get_id (IdeDevice *self);
+void ide_device_set_id (IdeDevice *self,
+ const gchar *id);
+const gchar *ide_device_get_system_type (IdeDevice *self);
+void ide_device_prepare_configuration (IdeDevice *self,
+ IdeConfiguration *configuration);
G_END_DECLS
diff --git a/libide/ide-environment-editor-row.c b/libide/ide-environment-editor-row.c
new file mode 100644
index 0000000..4db6d40
--- /dev/null
+++ b/libide/ide-environment-editor-row.c
@@ -0,0 +1,272 @@
+/* ide-environment-editor-row.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-environment-editor-row.h"
+
+struct _IdeEnvironmentEditorRow
+{
+ GtkListBoxRow parent_instance;
+
+ IdeEnvironmentVariable *variable;
+
+ GtkEntry *key_entry;
+ GtkEntry *value_entry;
+ GtkButton *delete_button;
+
+ GBinding *key_binding;
+ GBinding *value_binding;
+};
+
+enum {
+ PROP_0,
+ PROP_VARIABLE,
+ LAST_PROP
+};
+
+enum {
+ DELETE,
+ LAST_SIGNAL
+};
+
+G_DEFINE_TYPE (IdeEnvironmentEditorRow, ide_environment_editor_row, GTK_TYPE_LIST_BOX_ROW)
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+static gboolean
+null_safe_mapping (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ const gchar *str = g_value_get_string (from_value);
+ g_value_set_string (to_value, str ?: "");
+ return TRUE;
+}
+
+static void
+ide_environment_editor_row_connect (IdeEnvironmentEditorRow *self)
+{
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR_ROW (self));
+ g_assert (IDE_IS_ENVIRONMENT_VARIABLE (self->variable));
+
+ self->key_binding =
+ g_object_bind_property_full (self->variable, "key", self->key_entry, "text",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+ null_safe_mapping, NULL, NULL, NULL);
+
+ self->value_binding =
+ g_object_bind_property_full (self->variable, "value", self->value_entry, "text",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+ null_safe_mapping, NULL, NULL, NULL);
+}
+
+static void
+ide_environment_editor_row_disconnect (IdeEnvironmentEditorRow *self)
+{
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR_ROW (self));
+ g_assert (IDE_IS_ENVIRONMENT_VARIABLE (self->variable));
+
+ g_clear_pointer (&self->key_binding, g_binding_unbind);
+ g_clear_pointer (&self->value_binding, g_binding_unbind);
+}
+
+static void
+delete_button_clicked (GtkButton *button,
+ IdeEnvironmentEditorRow *self)
+{
+ g_assert (GTK_IS_BUTTON (button));
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR_ROW (self));
+
+ g_signal_emit (self, signals [DELETE], 0);
+}
+
+static void
+key_entry_activate (GtkWidget *entry,
+ IdeEnvironmentEditorRow *self)
+{
+ g_assert (GTK_IS_ENTRY (entry));
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR_ROW (self));
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->value_entry));
+}
+
+static void
+value_entry_activate (GtkWidget *entry,
+ IdeEnvironmentEditorRow *self)
+{
+ GtkWidget *parent;
+
+ g_assert (GTK_IS_ENTRY (entry));
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR_ROW (self));
+
+ gtk_widget_grab_focus (GTK_WIDGET (self));
+ parent = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_LIST_BOX);
+ g_signal_emit_by_name (parent, "move-cursor", GTK_MOVEMENT_DISPLAY_LINES, 1);
+}
+
+static void
+ide_environment_editor_row_destroy (GtkWidget *widget)
+{
+ IdeEnvironmentEditorRow *self = (IdeEnvironmentEditorRow *)widget;
+
+ if (self->variable != NULL)
+ {
+ ide_environment_editor_row_disconnect (self);
+ g_clear_object (&self->variable);
+ }
+
+ GTK_WIDGET_CLASS (ide_environment_editor_row_parent_class)->destroy (widget);
+}
+
+static void
+ide_environment_editor_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEnvironmentEditorRow *self = IDE_ENVIRONMENT_EDITOR_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_VARIABLE:
+ g_value_set_object (value, ide_environment_editor_row_get_variable (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_environment_editor_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEnvironmentEditorRow *self = IDE_ENVIRONMENT_EDITOR_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_VARIABLE:
+ ide_environment_editor_row_set_variable (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_environment_editor_row_class_init (IdeEnvironmentEditorRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = ide_environment_editor_row_get_property;
+ object_class->set_property = ide_environment_editor_row_set_property;
+
+ widget_class->destroy = ide_environment_editor_row_destroy;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/builder/ui/ide-environment-editor-row.ui");
+ gtk_widget_class_bind_template_child (widget_class, IdeEnvironmentEditorRow, delete_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEnvironmentEditorRow, key_entry);
+ gtk_widget_class_bind_template_child (widget_class, IdeEnvironmentEditorRow, value_entry);
+
+ properties [PROP_VARIABLE] =
+ g_param_spec_object ("variable",
+ "Variable",
+ "Variable",
+ IDE_TYPE_ENVIRONMENT_VARIABLE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals [DELETE] =
+ g_signal_new ("delete",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+}
+
+static void
+ide_environment_editor_row_init (IdeEnvironmentEditorRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect (self->delete_button,
+ "clicked",
+ G_CALLBACK (delete_button_clicked),
+ self);
+
+ g_signal_connect (self->key_entry,
+ "activate",
+ G_CALLBACK (key_entry_activate),
+ self);
+
+ g_signal_connect (self->value_entry,
+ "activate",
+ G_CALLBACK (value_entry_activate),
+ self);
+}
+
+/**
+ * ide_environment_editor_row_get_variable:
+ *
+ * Returns: (transfer none) (nullable): An #IdeEnvironmentVariable.
+ */
+IdeEnvironmentVariable *
+ide_environment_editor_row_get_variable (IdeEnvironmentEditorRow *self)
+{
+ g_return_val_if_fail (IDE_IS_ENVIRONMENT_EDITOR_ROW (self), NULL);
+
+ return self->variable;
+}
+
+void
+ide_environment_editor_row_set_variable (IdeEnvironmentEditorRow *self,
+ IdeEnvironmentVariable *variable)
+{
+ g_return_if_fail (IDE_IS_ENVIRONMENT_EDITOR_ROW (self));
+ g_return_if_fail (!variable || IDE_IS_ENVIRONMENT_VARIABLE (variable));
+
+ if (variable != self->variable)
+ {
+ if (self->variable != NULL)
+ {
+ ide_environment_editor_row_disconnect (self);
+ g_clear_object (&self->variable);
+ }
+
+ if (variable != NULL)
+ {
+ self->variable = g_object_ref (variable);
+ ide_environment_editor_row_connect (self);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VARIABLE]);
+ }
+}
+
+void
+ide_environment_editor_row_start_editing (IdeEnvironmentEditorRow *self)
+{
+ g_return_if_fail (IDE_IS_ENVIRONMENT_EDITOR_ROW (self));
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->key_entry));
+}
diff --git a/libide/ide-environment-editor-row.h b/libide/ide-environment-editor-row.h
new file mode 100644
index 0000000..309b042
--- /dev/null
+++ b/libide/ide-environment-editor-row.h
@@ -0,0 +1,39 @@
+/* ide-environment-editor-row.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_ENVIRONMENT_EDITOR_ROW_H
+#define IDE_ENVIRONMENT_EDITOR_ROW_H
+
+#include <gtk/gtk.h>
+
+#include "ide-environment-variable.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_ENVIRONMENT_EDITOR_ROW (ide_environment_editor_row_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEnvironmentEditorRow, ide_environment_editor_row, IDE, ENVIRONMENT_EDITOR_ROW,
GtkListBoxRow)
+
+IdeEnvironmentVariable *ide_environment_editor_row_get_variable (IdeEnvironmentEditorRow *self);
+void ide_environment_editor_row_set_variable (IdeEnvironmentEditorRow *self,
+ IdeEnvironmentVariable *variable);
+void ide_environment_editor_row_start_editing (IdeEnvironmentEditorRow *self);
+
+G_END_DECLS
+
+#endif /* IDE_ENVIRONMENT_EDITOR_ROW_H */
diff --git a/libide/ide-environment-editor.c b/libide/ide-environment-editor.c
new file mode 100644
index 0000000..0128a83
--- /dev/null
+++ b/libide/ide-environment-editor.c
@@ -0,0 +1,311 @@
+/* ide-environment-editor.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "ide-environment-editor.h"
+#include "ide-environment-editor-row.h"
+
+struct _IdeEnvironmentEditor
+{
+ GtkListBox parent_instance;
+ IdeEnvironment *environment;
+ GtkWidget *dummy_row;
+
+ IdeEnvironmentVariable *dummy;
+};
+
+G_DEFINE_TYPE (IdeEnvironmentEditor, ide_environment_editor, GTK_TYPE_LIST_BOX)
+
+enum {
+ PROP_0,
+ PROP_ENVIRONMENT,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_environment_editor_delete_row (IdeEnvironmentEditor *self,
+ IdeEnvironmentEditorRow *row)
+{
+ IdeEnvironmentVariable *variable;
+
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR (self));
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR_ROW (row));
+
+ variable = ide_environment_editor_row_get_variable (row);
+ ide_environment_remove (self->environment, variable);
+}
+
+static GtkWidget *
+ide_environment_editor_create_dummy_row (IdeEnvironmentEditor *self)
+{
+ GtkWidget *row;
+ GtkWidget *label;
+
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR (self));
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "label", _("New variable…"),
+ "visible", TRUE,
+ "xalign", 0.0f,
+ NULL);
+ gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
+
+ row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+ "child", label,
+ "visible", TRUE,
+ NULL);
+
+ return row;
+}
+
+static GtkWidget *
+ide_environment_editor_create_row (gpointer item,
+ gpointer user_data)
+{
+ IdeEnvironmentVariable *variable = item;
+ IdeEnvironmentEditor *self = user_data;
+ IdeEnvironmentEditorRow *row;
+
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR (self));
+ g_assert (IDE_IS_ENVIRONMENT_VARIABLE (variable));
+
+ row = g_object_new (IDE_TYPE_ENVIRONMENT_EDITOR_ROW,
+ "variable", variable,
+ "visible", TRUE,
+ NULL);
+
+ g_signal_connect_object (row,
+ "delete",
+ G_CALLBACK (ide_environment_editor_delete_row),
+ self,
+ G_CONNECT_SWAPPED);
+
+ return GTK_WIDGET (row);
+}
+
+static void
+ide_environment_editor_disconnect (IdeEnvironmentEditor *self)
+{
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR (self));
+ g_assert (IDE_IS_ENVIRONMENT (self->environment));
+
+ gtk_list_box_bind_model (GTK_LIST_BOX (self), NULL, NULL, NULL, NULL);
+
+ g_clear_object (&self->dummy);
+}
+
+static void
+ide_environment_editor_connect (IdeEnvironmentEditor *self)
+{
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR (self));
+ g_assert (IDE_IS_ENVIRONMENT (self->environment));
+
+ gtk_list_box_bind_model (GTK_LIST_BOX (self),
+ G_LIST_MODEL (self->environment),
+ ide_environment_editor_create_row, self, NULL);
+
+ self->dummy_row = ide_environment_editor_create_dummy_row (self);
+ gtk_container_add (GTK_CONTAINER (self), self->dummy_row);
+}
+
+static void
+find_row_cb (GtkWidget *widget,
+ gpointer data)
+{
+ struct {
+ IdeEnvironmentVariable *variable;
+ IdeEnvironmentEditorRow *row;
+ } *lookup = data;
+
+ g_assert (lookup != NULL);
+ g_assert (GTK_IS_LIST_BOX_ROW (widget));
+
+ if (IDE_IS_ENVIRONMENT_EDITOR_ROW (widget))
+ {
+ IdeEnvironmentVariable *variable;
+
+ variable = ide_environment_editor_row_get_variable (IDE_ENVIRONMENT_EDITOR_ROW (widget));
+
+ if (variable == lookup->variable)
+ lookup->row = IDE_ENVIRONMENT_EDITOR_ROW (widget);
+ }
+}
+
+static IdeEnvironmentEditorRow *
+find_row (IdeEnvironmentEditor *self,
+ IdeEnvironmentVariable *variable)
+{
+ struct {
+ IdeEnvironmentVariable *variable;
+ IdeEnvironmentEditorRow *row;
+ } lookup = { variable, NULL };
+
+ g_assert (IDE_IS_ENVIRONMENT_EDITOR (self));
+ g_assert (IDE_IS_ENVIRONMENT_VARIABLE (variable));
+
+ gtk_container_foreach (GTK_CONTAINER (self), find_row_cb, &lookup);
+
+ return lookup.row;
+}
+
+static void
+ide_environment_editor_row_activated (GtkListBox *list_box,
+ GtkListBoxRow *row)
+{
+ IdeEnvironmentEditor *self = (IdeEnvironmentEditor *)list_box;
+
+ g_assert (GTK_IS_LIST_BOX (list_box));
+ g_assert (GTK_IS_LIST_BOX_ROW (row));
+
+ if (self->environment == NULL)
+ return;
+
+ if (self->dummy_row == GTK_WIDGET (row))
+ {
+ g_autoptr(IdeEnvironmentVariable) variable = NULL;
+
+ variable = ide_environment_variable_new (NULL, NULL);
+ ide_environment_append (self->environment, variable);
+ ide_environment_editor_row_start_editing (find_row (self, variable));
+ }
+}
+
+static void
+ide_environment_editor_destroy (GtkWidget *widget)
+{
+ IdeEnvironmentEditor *self = (IdeEnvironmentEditor *)widget;
+
+ GTK_WIDGET_CLASS (ide_environment_editor_parent_class)->destroy (widget);
+
+ g_clear_object (&self->environment);
+}
+
+static void
+ide_environment_editor_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEnvironmentEditor *self = IDE_ENVIRONMENT_EDITOR(object);
+
+ switch (prop_id)
+ {
+ case PROP_ENVIRONMENT:
+ g_value_set_object (value, ide_environment_editor_get_environment (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+ide_environment_editor_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEnvironmentEditor *self = IDE_ENVIRONMENT_EDITOR(object);
+
+ switch (prop_id)
+ {
+ case PROP_ENVIRONMENT:
+ ide_environment_editor_set_environment (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+ide_environment_editor_class_init (IdeEnvironmentEditorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkListBoxClass *list_box_class = GTK_LIST_BOX_CLASS (klass);
+
+ object_class->get_property = ide_environment_editor_get_property;
+ object_class->set_property = ide_environment_editor_set_property;
+
+ widget_class->destroy = ide_environment_editor_destroy;
+
+ list_box_class->row_activated = ide_environment_editor_row_activated;
+
+ properties [PROP_ENVIRONMENT] =
+ g_param_spec_object ("environment",
+ "Environment",
+ "Environment",
+ IDE_TYPE_ENVIRONMENT,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_environment_editor_init (IdeEnvironmentEditor *self)
+{
+ gtk_list_box_set_selection_mode (GTK_LIST_BOX (self), GTK_SELECTION_NONE);
+}
+
+GtkWidget *
+ide_environment_editor_new (void)
+{
+ return g_object_new (IDE_TYPE_ENVIRONMENT_EDITOR, NULL);
+}
+
+void
+ide_environment_editor_set_environment (IdeEnvironmentEditor *self,
+ IdeEnvironment *environment)
+{
+ g_return_if_fail (IDE_IS_ENVIRONMENT_EDITOR (self));
+ g_return_if_fail (IDE_IS_ENVIRONMENT (environment));
+
+ if (self->environment != environment)
+ {
+ if (self->environment != NULL)
+ {
+ ide_environment_editor_disconnect (self);
+ g_clear_object (&self->environment);
+ }
+
+ if (environment != NULL)
+ {
+ self->environment = g_object_ref (environment);
+ ide_environment_editor_connect (self);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ENVIRONMENT]);
+ }
+}
+
+/**
+ * ide_environment_editor_get_environment:
+ *
+ * Returns: (nullable) (transfer none): An #IdeEnvironment or %NULL.
+ */
+IdeEnvironment *
+ide_environment_editor_get_environment (IdeEnvironmentEditor *self)
+{
+ g_return_val_if_fail (IDE_IS_ENVIRONMENT_EDITOR (self), NULL);
+
+ return self->environment;
+}
diff --git a/libide/ide-environment-editor.h b/libide/ide-environment-editor.h
new file mode 100644
index 0000000..5862bed
--- /dev/null
+++ b/libide/ide-environment-editor.h
@@ -0,0 +1,39 @@
+/* ide-environment-editor.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_ENVIRONMENT_EDITOR_H
+#define IDE_ENVIRONMENT_EDITOR_H
+
+#include <gtk/gtk.h>
+
+#include "ide-environment.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_ENVIRONMENT_EDITOR (ide_environment_editor_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEnvironmentEditor, ide_environment_editor, IDE, ENVIRONMENT_EDITOR, GtkListBox)
+
+GtkWidget *ide_environment_editor_new (void);
+IdeEnvironment *ide_environment_editor_get_environment (IdeEnvironmentEditor *self);
+void ide_environment_editor_set_environment (IdeEnvironmentEditor *self,
+ IdeEnvironment *environment);
+
+G_END_DECLS
+
+#endif /* IDE_ENVIRONMENT_EDITOR_H */
diff --git a/libide/ide-environment-variable.c b/libide/ide-environment-variable.c
new file mode 100644
index 0000000..58d6da7
--- /dev/null
+++ b/libide/ide-environment-variable.c
@@ -0,0 +1,179 @@
+/* ide-environment-variable.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-environment-variable.h"
+
+struct _IdeEnvironmentVariable
+{
+ GObject parent_instance;
+ gchar *key;
+ gchar *value;
+};
+
+G_DEFINE_TYPE (IdeEnvironmentVariable, ide_environment_variable, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_KEY,
+ PROP_VALUE,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_environment_variable_finalize (GObject *object)
+{
+ IdeEnvironmentVariable *self = (IdeEnvironmentVariable *)object;
+
+ g_clear_pointer (&self->key, g_free);
+ g_clear_pointer (&self->value, g_free);
+
+ G_OBJECT_CLASS (ide_environment_variable_parent_class)->finalize (object);
+}
+
+static void
+ide_environment_variable_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEnvironmentVariable *self = IDE_ENVIRONMENT_VARIABLE(object);
+
+ switch (prop_id)
+ {
+ case PROP_KEY:
+ g_value_set_string (value, self->key);
+ break;
+
+ case PROP_VALUE:
+ g_value_set_string (value, self->value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+ide_environment_variable_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEnvironmentVariable *self = IDE_ENVIRONMENT_VARIABLE(object);
+
+ switch (prop_id)
+ {
+ case PROP_KEY:
+ ide_environment_variable_set_key (self, g_value_get_string (value));
+ break;
+
+ case PROP_VALUE:
+ ide_environment_variable_set_value (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+ide_environment_variable_class_init (IdeEnvironmentVariableClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_environment_variable_finalize;
+ object_class->get_property = ide_environment_variable_get_property;
+ object_class->set_property = ide_environment_variable_set_property;
+
+ properties [PROP_KEY] =
+ g_param_spec_string ("key",
+ "Key",
+ "The key for the environment variable",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_VALUE] =
+ g_param_spec_string ("value",
+ "Value",
+ "The value for the environment variable",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_environment_variable_init (IdeEnvironmentVariable *self)
+{
+}
+
+const gchar *
+ide_environment_variable_get_key (IdeEnvironmentVariable *self)
+{
+ g_return_val_if_fail (IDE_IS_ENVIRONMENT_VARIABLE (self), NULL);
+
+ return self->key;
+}
+
+void
+ide_environment_variable_set_key (IdeEnvironmentVariable *self,
+ const gchar *key)
+{
+ g_return_if_fail (IDE_IS_ENVIRONMENT_VARIABLE (self));
+
+ if (g_strcmp0 (key, self->key) != 0)
+ {
+ g_free (self->key);
+ self->key = g_strdup (key);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_KEY]);
+ }
+}
+
+const gchar *
+ide_environment_variable_get_value (IdeEnvironmentVariable *self)
+{
+ g_return_val_if_fail (IDE_IS_ENVIRONMENT_VARIABLE (self), NULL);
+
+ return self->value;
+}
+
+void
+ide_environment_variable_set_value (IdeEnvironmentVariable *self,
+ const gchar *value)
+{
+ g_return_if_fail (IDE_IS_ENVIRONMENT_VARIABLE (self));
+
+ if (g_strcmp0 (value, self->value) != 0)
+ {
+ g_free (self->value);
+ self->value = g_strdup (value);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VALUE]);
+ }
+}
+
+IdeEnvironmentVariable *
+ide_environment_variable_new (const gchar *key,
+ const gchar *value)
+{
+ return g_object_new (IDE_TYPE_ENVIRONMENT_VARIABLE,
+ "key", key,
+ "value", value,
+ NULL);
+}
diff --git a/libide/ide-environment-variable.h b/libide/ide-environment-variable.h
new file mode 100644
index 0000000..910086b
--- /dev/null
+++ b/libide/ide-environment-variable.h
@@ -0,0 +1,41 @@
+/* ide-environment-variable.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_ENVIRONMENT_VARIABLE_H
+#define IDE_ENVIRONMENT_VARIABLE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_ENVIRONMENT_VARIABLE (ide_environment_variable_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEnvironmentVariable, ide_environment_variable, IDE, ENVIRONMENT_VARIABLE, GObject)
+
+IdeEnvironmentVariable *ide_environment_variable_new (const gchar *key,
+ const gchar *value);
+const gchar *ide_environment_variable_get_key (IdeEnvironmentVariable *self);
+void ide_environment_variable_set_key (IdeEnvironmentVariable *self,
+ const gchar *key);
+const gchar *ide_environment_variable_get_value (IdeEnvironmentVariable *self);
+void ide_environment_variable_set_value (IdeEnvironmentVariable *self,
+ const gchar *value);
+
+G_END_DECLS
+
+#endif /* IDE_ENVIRONMENT_VARIABLE_H */
diff --git a/libide/ide-environment.c b/libide/ide-environment.c
new file mode 100644
index 0000000..3bdbdae
--- /dev/null
+++ b/libide/ide-environment.c
@@ -0,0 +1,265 @@
+/* ide-environment.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-environment.h"
+#include "ide-environment-variable.h"
+
+struct _IdeEnvironment
+{
+ GObject parent_instance;
+ GPtrArray *variables;
+};
+
+static void list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeEnvironment, ide_environment, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static void
+ide_environment_finalize (GObject *object)
+{
+ IdeEnvironment *self = (IdeEnvironment *)object;
+
+ g_clear_pointer (&self->variables, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (ide_environment_parent_class)->finalize (object);
+}
+
+static void
+ide_environment_class_init (IdeEnvironmentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_environment_finalize;
+}
+
+static void
+ide_environment_init (IdeEnvironment *self)
+{
+ self->variables = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+static GType
+ide_environment_get_item_type (GListModel *model)
+{
+ return IDE_TYPE_ENVIRONMENT_VARIABLE;
+}
+
+static gpointer
+ide_environment_get_item (GListModel *model,
+ guint position)
+{
+ IdeEnvironment *self = (IdeEnvironment *)model;
+
+ g_return_val_if_fail (IDE_IS_ENVIRONMENT (self), NULL);
+ g_return_val_if_fail (position < self->variables->len, NULL);
+
+ return g_object_ref (g_ptr_array_index (self->variables, position));
+}
+
+static guint
+ide_environment_get_n_items (GListModel *model)
+{
+ IdeEnvironment *self = (IdeEnvironment *)model;
+
+ g_return_val_if_fail (IDE_IS_ENVIRONMENT (self), 0);
+
+ return self->variables->len;
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_n_items = ide_environment_get_n_items;
+ iface->get_item = ide_environment_get_item;
+ iface->get_item_type = ide_environment_get_item_type;
+}
+
+void
+ide_environment_setenv (IdeEnvironment *self,
+ const gchar *key,
+ const gchar *value)
+{
+ guint i;
+
+ g_return_if_fail (IDE_IS_ENVIRONMENT (self));
+ g_return_if_fail (key != NULL);
+
+ for (i = 0; i < self->variables->len; i++)
+ {
+ IdeEnvironmentVariable *var = g_ptr_array_index (self->variables, i);
+ const gchar *var_key = ide_environment_variable_get_key (var);
+
+ if (g_strcmp0 (key, var_key) == 0)
+ {
+ if (value == NULL)
+ {
+ g_ptr_array_remove_index (self->variables, i);
+ g_list_model_items_changed (G_LIST_MODEL (self), i, 1, 0);
+ return;
+ }
+
+ ide_environment_variable_set_value (var, value);
+ return;
+ }
+ }
+
+ if (value != NULL)
+ {
+ IdeEnvironmentVariable *var;
+ guint position = self->variables->len;
+
+ var = g_object_new (IDE_TYPE_ENVIRONMENT_VARIABLE,
+ "key", key,
+ "value", value,
+ NULL);
+ g_ptr_array_add (self->variables, var);
+ g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
+ }
+}
+
+const gchar *
+ide_environment_getenv (IdeEnvironment *self,
+ const gchar *key)
+{
+ guint i;
+
+ g_return_val_if_fail (IDE_IS_ENVIRONMENT (self), NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+
+ for (i = 0; i < self->variables->len; i++)
+ {
+ IdeEnvironmentVariable *var = g_ptr_array_index (self->variables, i);
+ const gchar *var_key = ide_environment_variable_get_key (var);
+
+ if (g_strcmp0 (key, var_key) == 0)
+ return ide_environment_variable_get_value (var);
+ }
+
+ return NULL;
+}
+
+/**
+ * ide_environment_get_environ:
+ * @self: An #IdeEnvironment
+ *
+ * Gets the environment as a set of key=value pairs, suitable for use
+ * in various GLib process functions.
+ *
+ * Returns: (transfer full): A newly allocated string array.
+ */
+gchar **
+ide_environment_get_environ (IdeEnvironment *self)
+{
+ GPtrArray *ar;
+ guint i;
+
+ g_return_val_if_fail (IDE_IS_ENVIRONMENT (self), NULL);
+
+ ar = g_ptr_array_new ();
+
+ for (i = 0; i < self->variables->len; i++)
+ {
+ IdeEnvironmentVariable *var = g_ptr_array_index (self->variables, i);
+ const gchar *key = ide_environment_variable_get_key (var);
+ const gchar *value = ide_environment_variable_get_value (var);
+
+ if (value == NULL)
+ value = "";
+
+ if (key != NULL)
+ g_ptr_array_add (ar, g_strdup_printf ("%s=%s", key, value));
+ }
+
+ g_ptr_array_add (ar, NULL);
+
+ return (gchar **)g_ptr_array_free (ar, FALSE);
+}
+
+IdeEnvironment *
+ide_environment_new (void)
+{
+ return g_object_new (IDE_TYPE_ENVIRONMENT, NULL);
+}
+
+void
+ide_environment_remove (IdeEnvironment *self,
+ IdeEnvironmentVariable *variable)
+{
+ guint i;
+
+ g_return_if_fail (IDE_IS_ENVIRONMENT (self));
+ g_return_if_fail (IDE_IS_ENVIRONMENT_VARIABLE (variable));
+
+ for (i = 0; i < self->variables->len; i++)
+ {
+ IdeEnvironmentVariable *item = g_ptr_array_index (self->variables, i);
+
+ if (item == variable)
+ {
+ g_ptr_array_remove_index (self->variables, i);
+ g_list_model_items_changed (G_LIST_MODEL (self), i, 1, 0);
+ break;
+ }
+ }
+}
+
+void
+ide_environment_append (IdeEnvironment *self,
+ IdeEnvironmentVariable *variable)
+{
+ guint position;
+
+ g_return_if_fail (IDE_IS_ENVIRONMENT (self));
+ g_return_if_fail (IDE_IS_ENVIRONMENT_VARIABLE (variable));
+
+ position = self->variables->len;
+
+ g_ptr_array_add (self->variables, g_object_ref (variable));
+ g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
+}
+
+/**
+ * ide_environment_copy:
+ * @self: An #IdeEnvironment
+ *
+ * Copies the contents of #IdeEnvironment into a newly allocated #IdeEnvironment.
+ *
+ * Returns: (transfer full): An #IdeEnvironment.
+ */
+IdeEnvironment *
+ide_environment_copy (IdeEnvironment *self)
+{
+ IdeEnvironment *copy;
+ guint i;
+
+ g_return_val_if_fail (IDE_IS_ENVIRONMENT (self), NULL);
+
+ copy = ide_environment_new ();
+
+ for (i = 0; i < self->variables->len; i++)
+ {
+ IdeEnvironmentVariable *var = g_ptr_array_index (self->variables, i);
+ const gchar *key = ide_environment_variable_get_key (var);
+ const gchar *value = ide_environment_variable_get_value (var);
+
+ ide_environment_setenv (copy, key, value);
+ }
+
+ return copy;
+}
diff --git a/libide/ide-environment.h b/libide/ide-environment.h
new file mode 100644
index 0000000..823786c
--- /dev/null
+++ b/libide/ide-environment.h
@@ -0,0 +1,47 @@
+/* ide-environment.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_ENVIRONMENT_H
+#define IDE_ENVIRONMENT_H
+
+#include <gio/gio.h>
+
+#include "ide-environment-variable.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_ENVIRONMENT (ide_environment_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEnvironment, ide_environment, IDE, ENVIRONMENT, GObject)
+
+IdeEnvironment *ide_environment_new (void);
+void ide_environment_setenv (IdeEnvironment *self,
+ const gchar *key,
+ const gchar *value);
+const gchar *ide_environment_getenv (IdeEnvironment *self,
+ const gchar *key);
+gchar **ide_environment_get_environ (IdeEnvironment *self);
+void ide_environment_append (IdeEnvironment *self,
+ IdeEnvironmentVariable *variable);
+void ide_environment_remove (IdeEnvironment *self,
+ IdeEnvironmentVariable *variable);
+IdeEnvironment *ide_environment_copy (IdeEnvironment *self);
+
+G_END_DECLS
+
+#endif /* IDE_ENVIRONMENT_H */
diff --git a/libide/ide-internal.h b/libide/ide-internal.h
index c9c5c9b..c040fde 100644
--- a/libide/ide-internal.h
+++ b/libide/ide-internal.h
@@ -59,6 +59,7 @@ IdeFixit *_ide_fixit_new (IdeSourceRange
const gchar *replacement_text);
void _ide_project_set_name (IdeProject *project,
const gchar *name);
+void _ide_runtime_manager_unload (IdeRuntimeManager *self);
void _ide_search_context_add_provider (IdeSearchContext *context,
IdeSearchProvider *provider,
gsize max_results);
diff --git a/libide/ide-runtime-manager.c b/libide/ide-runtime-manager.c
new file mode 100644
index 0000000..6f76442
--- /dev/null
+++ b/libide/ide-runtime-manager.c
@@ -0,0 +1,239 @@
+/* ide-runtime-manager.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-runtime-manager"
+
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+
+#include "ide-runtime.h"
+#include "ide-runtime-manager.h"
+#include "ide-runtime-provider.h"
+
+struct _IdeRuntimeManager
+{
+ IdeObject parent_instance;
+ PeasExtensionSet *extensions;
+ GPtrArray *runtimes;
+};
+
+static void list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeRuntimeManager, ide_runtime_manager, IDE_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static void
+ide_runtime_manager_extension_added (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeRuntimeManager *self = user_data;
+ IdeRuntimeProvider *provider = (IdeRuntimeProvider *)exten;
+
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (plugin_info != NULL);
+ g_assert (IDE_IS_RUNTIME_PROVIDER (provider));
+
+ ide_runtime_provider_load (provider, self);
+}
+
+static void
+ide_runtime_manager_extension_removed (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeRuntimeManager *self = user_data;
+ IdeRuntimeProvider *provider = (IdeRuntimeProvider *)exten;
+
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (plugin_info != NULL);
+ g_assert (IDE_IS_RUNTIME_PROVIDER (provider));
+
+ ide_runtime_provider_unload (provider, self);
+}
+
+static void
+ide_runtime_manager_constructed (GObject *object)
+{
+ IdeRuntimeManager *self = (IdeRuntimeManager *)object;
+ IdeContext *context;
+
+ G_OBJECT_CLASS (ide_runtime_manager_parent_class)->constructed (object);
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+
+ self->extensions = peas_extension_set_new (peas_engine_get_default (),
+ IDE_TYPE_RUNTIME_PROVIDER,
+ NULL);
+
+ g_signal_connect (self->extensions,
+ "extension-added",
+ G_CALLBACK (ide_runtime_manager_extension_added),
+ self);
+
+ g_signal_connect (self->extensions,
+ "extension-removed",
+ G_CALLBACK (ide_runtime_manager_extension_removed),
+ self);
+
+ peas_extension_set_foreach (self->extensions,
+ ide_runtime_manager_extension_added,
+ self);
+
+ ide_runtime_manager_add (self, ide_runtime_new (context, "host", _("Host operating system")));
+}
+
+void
+_ide_runtime_manager_unload (IdeRuntimeManager *self)
+{
+ g_return_if_fail (IDE_IS_RUNTIME_MANAGER (self));
+
+ g_clear_object (&self->extensions);
+}
+
+static void
+ide_runtime_manager_dispose (GObject *object)
+{
+ IdeRuntimeManager *self = (IdeRuntimeManager *)object;
+
+ g_clear_object (&self->extensions);
+ g_clear_pointer (&self->runtimes, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (ide_runtime_manager_parent_class)->dispose (object);
+}
+
+static void
+ide_runtime_manager_class_init (IdeRuntimeManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = ide_runtime_manager_constructed;
+ object_class->dispose = ide_runtime_manager_dispose;
+}
+
+static void
+ide_runtime_manager_init (IdeRuntimeManager *self)
+{
+ self->runtimes = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+static GType
+ide_runtime_manager_get_item_type (GListModel *model)
+{
+ return IDE_TYPE_RUNTIME;
+}
+
+static guint
+ide_runtime_manager_get_n_items (GListModel *model)
+{
+ IdeRuntimeManager *self = (IdeRuntimeManager *)model;
+
+ g_return_val_if_fail (IDE_IS_RUNTIME_MANAGER (self), 0);
+
+ return self->runtimes->len;
+}
+
+static gpointer
+ide_runtime_manager_get_item (GListModel *model,
+ guint position)
+{
+ IdeRuntimeManager *self = (IdeRuntimeManager *)model;
+
+ g_return_val_if_fail (IDE_IS_RUNTIME_MANAGER (self), NULL);
+ g_return_val_if_fail (position < self->runtimes->len, NULL);
+
+ return g_object_ref (g_ptr_array_index (self->runtimes, position));
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_item_type = ide_runtime_manager_get_item_type;
+ iface->get_n_items = ide_runtime_manager_get_n_items;
+ iface->get_item = ide_runtime_manager_get_item;
+}
+
+void
+ide_runtime_manager_add (IdeRuntimeManager *self,
+ IdeRuntime *runtime)
+{
+ guint idx;
+
+ g_return_if_fail (IDE_IS_RUNTIME_MANAGER (self));
+ g_return_if_fail (IDE_IS_RUNTIME (runtime));
+
+ idx = self->runtimes->len;
+ g_ptr_array_add (self->runtimes, g_object_ref (runtime));
+ g_list_model_items_changed (G_LIST_MODEL (self), idx, 0, 1);
+}
+
+void
+ide_runtime_manager_remove (IdeRuntimeManager *self,
+ IdeRuntime *runtime)
+{
+ guint i;
+
+ g_return_if_fail (IDE_IS_RUNTIME_MANAGER (self));
+ g_return_if_fail (IDE_IS_RUNTIME (runtime));
+
+ for (i = 0; i < self->runtimes->len; i++)
+ {
+ IdeRuntime *item = g_ptr_array_index (self->runtimes, i);
+
+ if (runtime == item)
+ {
+ g_ptr_array_remove_index (self->runtimes, i);
+ g_list_model_items_changed (G_LIST_MODEL (self), i, 1, 0);
+ break;
+ }
+ }
+}
+
+/**
+ * ide_runtime_manager_get_runtime:
+ * @self: An #IdeRuntimeManager
+ * @id: the identifier of the runtime
+ *
+ * Gets the runtime by it's internal identifier.
+ *
+ * Returns: (transfer none): An #IdeRuntime.
+ */
+IdeRuntime *
+ide_runtime_manager_get_runtime (IdeRuntimeManager *self,
+ const gchar *id)
+{
+ guint i;
+
+ g_return_val_if_fail (IDE_IS_RUNTIME_MANAGER (self), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+
+ for (i = 0; i < self->runtimes->len; i++)
+ {
+ IdeRuntime *runtime = g_ptr_array_index (self->runtimes, i);
+ const gchar *runtime_id;
+
+ runtime_id = ide_runtime_get_id (runtime);
+
+ if (g_strcmp0 (runtime_id, id) == 0)
+ return runtime;
+ }
+
+ return NULL;
+}
diff --git a/libide/ide-runtime-manager.h b/libide/ide-runtime-manager.h
new file mode 100644
index 0000000..f7fd591
--- /dev/null
+++ b/libide/ide-runtime-manager.h
@@ -0,0 +1,39 @@
+/* ide-runtime-manager.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_RUNTIME_MANAGER_H
+#define IDE_RUNTIME_MANAGER_H
+
+#include "ide-object.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUNTIME_MANAGER (ide_runtime_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeRuntimeManager, ide_runtime_manager, IDE, RUNTIME_MANAGER, IdeObject)
+
+IdeRuntime *ide_runtime_manager_get_runtime (IdeRuntimeManager *self,
+ const gchar *id);
+void ide_runtime_manager_add (IdeRuntimeManager *self,
+ IdeRuntime *runtime);
+void ide_runtime_manager_remove (IdeRuntimeManager *self,
+ IdeRuntime *runtime);
+
+G_END_DECLS
+
+#endif /* IDE_RUNTIME_MANAGER_H */
diff --git a/libide/ide-runtime-provider.c b/libide/ide-runtime-provider.c
new file mode 100644
index 0000000..41483e6
--- /dev/null
+++ b/libide/ide-runtime-provider.c
@@ -0,0 +1,61 @@
+/* ide-runtime-provider.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-runtime-manager.h"
+#include "ide-runtime-provider.h"
+
+G_DEFINE_INTERFACE (IdeRuntimeProvider, ide_runtime_provider, G_TYPE_OBJECT)
+
+static void
+ide_runtime_provider_real_load (IdeRuntimeProvider *self,
+ IdeRuntimeManager *manager)
+{
+}
+
+static void
+ide_runtime_provider_real_unload (IdeRuntimeProvider *self,
+ IdeRuntimeManager *manager)
+{
+}
+
+static void
+ide_runtime_provider_default_init (IdeRuntimeProviderInterface *iface)
+{
+ iface->load = ide_runtime_provider_real_load;
+ iface->unload = ide_runtime_provider_real_unload;
+}
+
+void
+ide_runtime_provider_load (IdeRuntimeProvider *self,
+ IdeRuntimeManager *manager)
+{
+ g_return_if_fail (IDE_IS_RUNTIME_PROVIDER (self));
+ g_return_if_fail (IDE_IS_RUNTIME_MANAGER (manager));
+
+ IDE_RUNTIME_PROVIDER_GET_IFACE (self)->load (self, manager);
+}
+
+void
+ide_runtime_provider_unload (IdeRuntimeProvider *self,
+ IdeRuntimeManager *manager)
+{
+ g_return_if_fail (IDE_IS_RUNTIME_PROVIDER (self));
+ g_return_if_fail (IDE_IS_RUNTIME_MANAGER (manager));
+
+ IDE_RUNTIME_PROVIDER_GET_IFACE (self)->unload (self, manager);
+}
diff --git a/libide/ide-runtime-provider.h b/libide/ide-runtime-provider.h
new file mode 100644
index 0000000..15098d9
--- /dev/null
+++ b/libide/ide-runtime-provider.h
@@ -0,0 +1,49 @@
+/* ide-runtime-provider.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_RUNTIME_PROVIDER_H
+#define IDE_RUNTIME_PROVIDER_H
+
+#include <gio/gio.h>
+
+#include "ide-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUNTIME_PROVIDER (ide_runtime_provider_get_type ())
+
+G_DECLARE_INTERFACE (IdeRuntimeProvider, ide_runtime_provider, IDE, RUNTIME_PROVIDER, GObject)
+
+struct _IdeRuntimeProviderInterface
+{
+ GTypeInterface parent;
+
+ void (*load) (IdeRuntimeProvider *self,
+ IdeRuntimeManager *manager);
+ void (*unload) (IdeRuntimeProvider *self,
+ IdeRuntimeManager *manager);
+};
+
+void ide_runtime_provider_load (IdeRuntimeProvider *self,
+ IdeRuntimeManager *manager);
+void ide_runtime_provider_unload (IdeRuntimeProvider *self,
+ IdeRuntimeManager *manager);
+
+G_END_DECLS
+
+#endif /* IDE_RUNTIME_PROVIDER_H */
diff --git a/libide/ide-runtime.c b/libide/ide-runtime.c
new file mode 100644
index 0000000..4bde63c
--- /dev/null
+++ b/libide/ide-runtime.c
@@ -0,0 +1,408 @@
+/* ide-runtime.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-runtime"
+
+#include "ide-builder.h"
+#include "ide-configuration.h"
+#include "ide-context.h"
+#include "ide-project.h"
+#include "ide-runtime.h"
+
+typedef struct
+{
+ gchar *id;
+ gchar *display_name;
+} IdeRuntimePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeRuntime, ide_runtime, IDE_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_ID,
+ PROP_DISPLAY_NAME,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_runtime_real_prebuild_async (IdeRuntime *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (IDE_IS_RUNTIME (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+ide_runtime_real_prebuild_finish (IdeRuntime *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (IDE_IS_RUNTIME (self));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+ide_runtime_real_postbuild_async (IdeRuntime *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (IDE_IS_RUNTIME (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+ide_runtime_real_postbuild_finish (IdeRuntime *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (IDE_IS_RUNTIME (self));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static IdeSubprocessLauncher *
+ide_runtime_real_create_launcher (IdeRuntime *self,
+ GError **error)
+{
+ IdeSubprocessLauncher *ret;
+ g_auto(GStrv) env = NULL;
+
+ g_assert (IDE_IS_RUNTIME (self));
+
+ env = g_get_environ ();
+
+ ret = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE);
+ ide_subprocess_launcher_set_environ (ret, (const gchar * const *)env);
+
+ return ret;
+}
+
+static gboolean
+ide_runtime_real_contains_program_in_path (IdeRuntime *self,
+ const gchar *program,
+ GCancellable *cancellable)
+{
+ gchar *path;
+ gboolean ret;
+
+ g_assert (IDE_IS_RUNTIME (self));
+ g_assert (program != NULL);
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ path = g_find_program_in_path (program);
+ ret = path != NULL;
+ g_free (path);
+
+ return ret;
+}
+
+gboolean
+ide_runtime_contains_program_in_path (IdeRuntime *self,
+ const gchar *program,
+ GCancellable *cancellable)
+{
+ g_return_val_if_fail (IDE_IS_RUNTIME (self), FALSE);
+ g_return_val_if_fail (program != NULL, FALSE);
+ g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+ return IDE_RUNTIME_GET_CLASS (self)->contains_program_in_path (self, program, cancellable);
+}
+
+static void
+ide_runtime_real_prepare_configuration (IdeRuntime *self,
+ IdeConfiguration *configuration)
+{
+ IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
+ g_autofree gchar *install_path = NULL;
+ IdeContext *context;
+ IdeProject *project;
+ const gchar *project_name;
+
+ g_assert (IDE_IS_RUNTIME (self));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ project = ide_context_get_project (context);
+ project_name = ide_project_get_name (project);
+
+ install_path = g_build_filename (g_get_user_cache_dir (),
+ "gnome-builder",
+ "install",
+ project_name,
+ priv->id,
+ NULL);
+
+ ide_configuration_set_prefix (configuration, install_path);
+}
+
+static void
+ide_runtime_finalize (GObject *object)
+{
+ IdeRuntime *self = (IdeRuntime *)object;
+ IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
+
+ g_clear_pointer (&priv->display_name, g_free);
+
+ G_OBJECT_CLASS (ide_runtime_parent_class)->finalize (object);
+}
+
+static void
+ide_runtime_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeRuntime *self = IDE_RUNTIME (object);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ g_value_set_string (value, ide_runtime_get_id (self));
+ break;
+
+ case PROP_DISPLAY_NAME:
+ g_value_set_string (value, ide_runtime_get_display_name (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_runtime_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeRuntime *self = IDE_RUNTIME (object);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ ide_runtime_set_id (self, g_value_get_string (value));
+ break;
+
+ case PROP_DISPLAY_NAME:
+ ide_runtime_set_display_name (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_runtime_class_init (IdeRuntimeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_runtime_finalize;
+ object_class->get_property = ide_runtime_get_property;
+ object_class->set_property = ide_runtime_set_property;
+
+ klass->prebuild_async = ide_runtime_real_prebuild_async;
+ klass->prebuild_finish = ide_runtime_real_prebuild_finish;
+ klass->postbuild_async = ide_runtime_real_postbuild_async;
+ klass->postbuild_finish = ide_runtime_real_postbuild_finish;
+ klass->create_launcher = ide_runtime_real_create_launcher;
+ klass->contains_program_in_path = ide_runtime_real_contains_program_in_path;
+ klass->prepare_configuration = ide_runtime_real_prepare_configuration;
+
+ properties [PROP_ID] =
+ g_param_spec_string ("id",
+ "Id",
+ "The runtime identifier",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DISPLAY_NAME] =
+ g_param_spec_string ("display-name",
+ "Display Name",
+ "Display Name",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_runtime_init (IdeRuntime *self)
+{
+}
+
+const gchar *
+ide_runtime_get_id (IdeRuntime *self)
+{
+ IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_RUNTIME (self), NULL);
+
+ return priv->id;
+}
+
+void
+ide_runtime_set_id (IdeRuntime *self,
+ const gchar *id)
+{
+ IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_RUNTIME (self));
+ g_return_if_fail (id != NULL);
+
+ if (0 != g_strcmp0 (id, priv->id))
+ {
+ g_free (priv->id);
+ priv->id = g_strdup (id);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ID]);
+ }
+}
+
+const gchar *
+ide_runtime_get_display_name (IdeRuntime *self)
+{
+ IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_RUNTIME (self), NULL);
+
+ return priv->display_name;
+}
+
+void
+ide_runtime_set_display_name (IdeRuntime *self,
+ const gchar *display_name)
+{
+ IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_RUNTIME (self));
+ g_return_if_fail (display_name != NULL);
+
+ if (g_strcmp0 (display_name, priv->display_name) != 0)
+ {
+ g_free (priv->display_name);
+ priv->display_name = g_strdup (display_name);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISPLAY_NAME]);
+ }
+}
+
+IdeRuntime *
+ide_runtime_new (IdeContext *context,
+ const gchar *id,
+ const gchar *display_name)
+{
+ return g_object_new (IDE_TYPE_RUNTIME,
+ "context", context,
+ "id", id,
+ "display-name", display_name,
+ NULL);
+}
+
+void
+ide_runtime_prebuild_async (IdeRuntime *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (IDE_IS_RUNTIME (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ IDE_RUNTIME_GET_CLASS (self)->prebuild_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_runtime_prebuild_finish (IdeRuntime *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_RUNTIME (self), FALSE);
+
+ return IDE_RUNTIME_GET_CLASS (self)->prebuild_finish (self, result, error);
+}
+
+void
+ide_runtime_postbuild_async (IdeRuntime *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (IDE_IS_RUNTIME (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ IDE_RUNTIME_GET_CLASS (self)->postbuild_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_runtime_postbuild_finish (IdeRuntime *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_RUNTIME (self), FALSE);
+
+ return IDE_RUNTIME_GET_CLASS (self)->postbuild_finish (self, result, error);
+}
+
+/**
+ * ide_runtime_create_launcher:
+ *
+ * Creates a launcher for the runtime.
+ *
+ * This can be used to execute a command within a runtime.
+ * If you are doing a build, you probably want to ensure you call
+ * ide_runtime_prebuild_async() before using the launcher.
+ *
+ * It is important that this function can be run from a thread without
+ * side effects.
+ *
+ * Returns: (transfer full): An #IdeSubprocessLauncher or %NULL upon failure.
+ */
+IdeSubprocessLauncher *
+ide_runtime_create_launcher (IdeRuntime *self,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_RUNTIME (self), NULL);
+
+ return IDE_RUNTIME_GET_CLASS (self)->create_launcher (self, error);
+}
+
+void
+ide_runtime_prepare_configuration (IdeRuntime *self,
+ IdeConfiguration *configuration)
+{
+ g_return_if_fail (IDE_IS_RUNTIME (self));
+ g_return_if_fail (IDE_IS_CONFIGURATION (configuration));
+
+ IDE_RUNTIME_GET_CLASS (self)->prepare_configuration (self, configuration);
+}
diff --git a/libide/ide-runtime.h b/libide/ide-runtime.h
new file mode 100644
index 0000000..c5aa19e
--- /dev/null
+++ b/libide/ide-runtime.h
@@ -0,0 +1,93 @@
+/* ide-runtime.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_RUNTIME_H
+#define IDE_RUNTIME_H
+
+#include <gio/gio.h>
+
+#include "ide-object.h"
+#include "ide-subprocess-launcher.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUNTIME (ide_runtime_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeRuntime, ide_runtime, IDE, RUNTIME, IdeObject)
+
+struct _IdeRuntimeClass
+{
+ IdeObjectClass parent;
+
+ void (*prebuild_async) (IdeRuntime *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (*prebuild_finish) (IdeRuntime *self,
+ GAsyncResult *result,
+ GError **error);
+ void (*postbuild_async) (IdeRuntime *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (*postbuild_finish) (IdeRuntime *self,
+ GAsyncResult *result,
+ GError **error);
+ gboolean (*contains_program_in_path) (IdeRuntime *self,
+ const gchar *program,
+ GCancellable *cancellable);
+ IdeSubprocessLauncher *(*create_launcher) (IdeRuntime *self,
+ GError **error);
+ void (*prepare_configuration) (IdeRuntime *self,
+ IdeConfiguration *configuration);
+};
+
+void ide_runtime_prebuild_async (IdeRuntime *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_runtime_prebuild_finish (IdeRuntime *self,
+ GAsyncResult *result,
+ GError **error);
+void ide_runtime_postbuild_async (IdeRuntime *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_runtime_postbuild_finish (IdeRuntime *self,
+ GAsyncResult *result,
+ GError **error);
+gboolean ide_runtime_contains_program_in_path (IdeRuntime *self,
+ const gchar *program,
+ GCancellable *cancellable);
+IdeSubprocessLauncher *ide_runtime_create_launcher (IdeRuntime *self,
+ GError **error);
+void ide_runtime_prepare_configuration (IdeRuntime *self,
+ IdeConfiguration *configuration);
+IdeRuntime *ide_runtime_new (IdeContext *context,
+ const gchar *id,
+ const gchar *title);
+const gchar *ide_runtime_get_id (IdeRuntime *self);
+void ide_runtime_set_id (IdeRuntime *self,
+ const gchar *id);
+const gchar *ide_runtime_get_display_name (IdeRuntime *self);
+void ide_runtime_set_display_name (IdeRuntime *self,
+ const gchar *display_name);
+
+G_END_DECLS
+
+#endif /* IDE_RUNTIME_H */
diff --git a/libide/ide-subprocess-launcher.c b/libide/ide-subprocess-launcher.c
new file mode 100644
index 0000000..345c7bb
--- /dev/null
+++ b/libide/ide-subprocess-launcher.c
@@ -0,0 +1,511 @@
+/* ide-subprocess-launcher.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "ide-debug.h"
+#include "ide-environment.h"
+#include "ide-environment-variable.h"
+#include "ide-macros.h"
+#include "ide-subprocess-launcher.h"
+
+typedef struct
+{
+ GSubprocessFlags flags;
+ guint freeze_check : 1;
+
+ GPtrArray *argv;
+ gchar *cwd;
+ GPtrArray *environ;
+} IdeSubprocessLauncherPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeSubprocessLauncher, ide_subprocess_launcher, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_CWD,
+ PROP_ENVIRON,
+ PROP_FLAGS,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+IdeSubprocessLauncher *
+ide_subprocess_launcher_new (GSubprocessFlags flags)
+{
+ return g_object_new (IDE_TYPE_SUBPROCESS_LAUNCHER,
+ "flags", flags,
+ NULL);
+}
+
+static void
+ide_subprocess_launcher_spawn_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ IdeSubprocessLauncher *self = source_object;
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+ g_autoptr(GSubprocessLauncher) launcher = NULL;
+ GSubprocess *ret;
+ GError *error = NULL;
+
+ g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+#ifdef IDE_ENABLE_TRACE
+ {
+ g_autofree gchar *str = NULL;
+ str = g_strjoinv (" ", (gchar **)priv->argv->pdata);
+ IDE_TRACE_MSG ("Launching '%s'", str);
+ }
+#endif
+
+ launcher = g_subprocess_launcher_new (priv->flags);
+ g_subprocess_launcher_set_cwd (launcher, priv->cwd);
+ if (priv->environ->len > 1)
+ g_subprocess_launcher_set_environ (launcher, (gchar **)priv->environ->pdata);
+ ret = g_subprocess_launcher_spawnv (launcher,
+ (const gchar * const *)priv->argv->pdata,
+ &error);
+
+ if (ret == NULL)
+ {
+ g_task_return_error (task, error);
+ return;
+ }
+
+ g_task_return_pointer (task, ret, g_object_unref);
+}
+
+static GSubprocess *
+ide_subprocess_launcher_real_spawn_sync (IdeSubprocessLauncher *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (IDE_IS_SUBPROCESS_LAUNCHER (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ priv->freeze_check = TRUE;
+
+ task = g_task_new (self, cancellable, NULL, NULL);
+ g_task_run_in_thread_sync (task, ide_subprocess_launcher_spawn_worker);
+
+ return g_task_propagate_pointer (task, error);
+}
+
+static void
+ide_subprocess_launcher_real_spawn_async (IdeSubprocessLauncher *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (IDE_IS_SUBPROCESS_LAUNCHER (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ priv->freeze_check = TRUE;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_run_in_thread (task, ide_subprocess_launcher_spawn_worker);
+}
+
+static GSubprocess *
+ide_subprocess_launcher_real_spawn_finish (IdeSubprocessLauncher *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (IDE_IS_SUBPROCESS_LAUNCHER (self));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+ide_subprocess_launcher_finalize (GObject *object)
+{
+ IdeSubprocessLauncher *self = (IdeSubprocessLauncher *)object;
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ g_clear_pointer (&priv->argv, g_ptr_array_unref);
+ g_clear_pointer (&priv->cwd, g_free);
+ g_clear_pointer (&priv->environ, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (ide_subprocess_launcher_parent_class)->finalize (object);
+}
+
+static void
+ide_subprocess_launcher_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeSubprocessLauncher *self = IDE_SUBPROCESS_LAUNCHER (object);
+
+ switch (prop_id)
+ {
+ case PROP_CWD:
+ g_value_set_string (value, ide_subprocess_launcher_get_cwd (self));
+ break;
+
+ case PROP_FLAGS:
+ g_value_set_flags (value, ide_subprocess_launcher_get_flags (self));
+ break;
+
+ case PROP_ENVIRON:
+ g_value_set_boxed (value, ide_subprocess_launcher_get_environ (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_subprocess_launcher_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeSubprocessLauncher *self = IDE_SUBPROCESS_LAUNCHER (object);
+
+ switch (prop_id)
+ {
+ case PROP_CWD:
+ ide_subprocess_launcher_set_cwd (self, g_value_get_string (value));
+ break;
+
+ case PROP_FLAGS:
+ ide_subprocess_launcher_set_flags (self, g_value_get_flags (value));
+ break;
+
+ case PROP_ENVIRON:
+ ide_subprocess_launcher_set_environ (self, g_value_get_boxed (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_subprocess_launcher_class_init (IdeSubprocessLauncherClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_subprocess_launcher_finalize;
+ object_class->get_property = ide_subprocess_launcher_get_property;
+ object_class->set_property = ide_subprocess_launcher_set_property;
+
+ klass->spawn_sync = ide_subprocess_launcher_real_spawn_sync;
+ klass->spawn_async = ide_subprocess_launcher_real_spawn_async;
+ klass->spawn_finish = ide_subprocess_launcher_real_spawn_finish;
+
+ properties [PROP_CWD] =
+ g_param_spec_string ("cwd",
+ "Current Working Directory",
+ "Current Working Directory",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_FLAGS] =
+ g_param_spec_flags ("flags",
+ "Flags",
+ "Flags",
+ G_TYPE_SUBPROCESS_FLAGS,
+ G_SUBPROCESS_FLAGS_NONE,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ENVIRON] =
+ g_param_spec_boxed ("environ",
+ "Environ",
+ "Environ",
+ G_TYPE_STRV,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_subprocess_launcher_init (IdeSubprocessLauncher *self)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ priv->environ = g_ptr_array_new_with_free_func (g_free);
+ g_ptr_array_add (priv->environ, NULL);
+
+ priv->argv = g_ptr_array_new_with_free_func (g_free);
+ g_ptr_array_add (priv->argv, NULL);
+
+ priv->cwd = g_strdup (".");
+}
+
+void
+ide_subprocess_launcher_set_flags (IdeSubprocessLauncher *self,
+ GSubprocessFlags flags)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+ if (priv->freeze_check)
+ {
+ g_warning ("process launcher is already frozen");
+ return;
+ }
+
+ if (flags != priv->flags)
+ {
+ priv->flags = flags;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FLAGS]);
+ }
+}
+
+GSubprocessFlags
+ide_subprocess_launcher_get_flags (IdeSubprocessLauncher *self)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), 0);
+
+ return priv->flags;
+}
+
+const gchar * const *
+ide_subprocess_launcher_get_environ (IdeSubprocessLauncher *self)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
+
+ return (const gchar * const *)priv->environ->pdata;
+}
+
+void
+ide_subprocess_launcher_set_environ (IdeSubprocessLauncher *self,
+ const gchar * const *environ_)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+ guint i;
+
+ g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+ if (priv->freeze_check)
+ {
+ g_warning ("process launcher is already frozen");
+ return;
+ }
+
+ g_ptr_array_remove_range (priv->environ, 0, priv->environ->len);
+
+ if (environ_ != NULL)
+ {
+ for (i = 0; environ_ [i]; i++)
+ g_ptr_array_add (priv->environ, g_strdup (environ_ [i]));
+ }
+
+ g_ptr_array_add (priv->environ, NULL);
+}
+
+void
+ide_subprocess_launcher_setenv (IdeSubprocessLauncher *self,
+ const gchar *key,
+ const gchar *value,
+ gboolean replace)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+ gchar *str;
+ guint i;
+
+ g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+ g_return_if_fail (key != NULL);
+
+ if (priv->freeze_check)
+ {
+ g_warning ("process launcher is already frozen");
+ return;
+ }
+
+ if (value == NULL)
+ value = "";
+
+ for (i = 0; i < priv->environ->len; i++)
+ {
+ gchar *item_key = g_ptr_array_index (priv->environ, i);
+ const gchar *eq;
+
+ if (item_key == NULL)
+ break;
+
+ if (NULL == (eq = strchr (item_key, '=')))
+ continue;
+
+ if (strncmp (item_key, key, eq - item_key) == 0)
+ {
+ if (replace)
+ {
+ g_free (item_key);
+ g_ptr_array_index (priv->environ, i) = g_strdup_printf ("%s=%s", key, value);
+ }
+ return;
+ }
+ }
+
+ str = g_strdup_printf ("%s=%s", key, value);
+ g_ptr_array_index (priv->environ, priv->environ->len - 1) = str;
+ g_ptr_array_add (priv->environ, NULL);
+}
+
+void
+ide_subprocess_launcher_push_argv (IdeSubprocessLauncher *self,
+ const gchar *argv)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+ g_return_if_fail (argv != NULL);
+
+ if (priv->freeze_check)
+ {
+ g_warning ("process launcher is already frozen");
+ return;
+ }
+
+ g_ptr_array_index (priv->argv, priv->argv->len - 1) = g_strdup (argv);
+ g_ptr_array_add (priv->argv, NULL);
+}
+
+void
+ide_subprocess_launcher_spawn_async (IdeSubprocessLauncher *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ IDE_SUBPROCESS_LAUNCHER_GET_CLASS (self)->spawn_async (self, cancellable, callback, user_data);
+}
+
+/**
+ * ide_subprocess_launcher_spawn_finish:
+ *
+ * Complete a request to asynchronously spawn a process.
+ *
+ * Returns: (transfer full): A #GSubprocess or %NULL upon error.
+ */
+GSubprocess *
+ide_subprocess_launcher_spawn_finish (IdeSubprocessLauncher *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
+ g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+ return IDE_SUBPROCESS_LAUNCHER_GET_CLASS (self)->spawn_finish (self, result, error);
+}
+
+/**
+ * ide_subprocess_launcher_spawn_sync:
+ *
+ * Synchronously spawn a process using the internal state.
+ *
+ * Returns: (transfer full): A #GSubprocess or %NULL upon error.
+ */
+GSubprocess *
+ide_subprocess_launcher_spawn_sync (IdeSubprocessLauncher *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
+ g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
+
+ return IDE_SUBPROCESS_LAUNCHER_GET_CLASS (self)->spawn_sync (self, cancellable, error);
+}
+
+void
+ide_subprocess_launcher_set_cwd (IdeSubprocessLauncher *self,
+ const gchar *cwd)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+ if (ide_str_empty0 (cwd))
+ cwd = ".";
+
+ if (!ide_str_equal0 (priv->cwd, cwd))
+ {
+ g_free (priv->cwd);
+ priv->cwd = g_strdup (cwd);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CWD]);
+ }
+}
+
+const gchar *
+ide_subprocess_launcher_get_cwd (IdeSubprocessLauncher *self)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
+
+ return priv->cwd;
+}
+
+void
+ide_subprocess_launcher_overlay_environment (IdeSubprocessLauncher *self,
+ IdeEnvironment *environment)
+{
+ g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+ g_return_if_fail (!environment || IDE_IS_ENVIRONMENT (environment));
+
+ if (environment != NULL)
+ {
+ guint n_items = g_list_model_get_n_items (G_LIST_MODEL (environment));
+
+ for (guint i = 0; i < n_items; i++)
+ {
+ g_autoptr(IdeEnvironmentVariable) var = NULL;
+ const gchar *key;
+ const gchar *value;
+
+ var = g_list_model_get_item (G_LIST_MODEL (environment), i);
+ key = ide_environment_variable_get_key (var);
+ value = ide_environment_variable_get_value (var);
+
+ if (!ide_str_empty0 (key))
+ ide_subprocess_launcher_setenv (self, key, value ?: "", TRUE);
+ }
+ }
+}
+
+void
+ide_subprocess_launcher_push_args (IdeSubprocessLauncher *self,
+ const gchar * const *args)
+{
+ g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+ g_return_if_fail (args != NULL);
+
+ for (guint i = 0; args [i] != NULL; i++)
+ ide_subprocess_launcher_push_argv (self, args [i]);
+}
diff --git a/libide/ide-subprocess-launcher.h b/libide/ide-subprocess-launcher.h
new file mode 100644
index 0000000..d733244
--- /dev/null
+++ b/libide/ide-subprocess-launcher.h
@@ -0,0 +1,81 @@
+/* ide-subprocess-launcher.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_SUBPROCESS_LAUNCHER_H
+#define IDE_SUBPROCESS_LAUNCHER_H
+
+#include <gio/gio.h>
+
+#include "ide-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SUBPROCESS_LAUNCHER (ide_subprocess_launcher_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeSubprocessLauncher, ide_subprocess_launcher, IDE, SUBPROCESS_LAUNCHER, GObject)
+
+struct _IdeSubprocessLauncherClass
+{
+ GObjectClass parent_class;
+
+ GSubprocess *(*spawn_sync) (IdeSubprocessLauncher *self,
+ GCancellable *cancellable,
+ GError **error);
+ void (*spawn_async) (IdeSubprocessLauncher *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ GSubprocess *(*spawn_finish) (IdeSubprocessLauncher *self,
+ GAsyncResult *result,
+ GError **error);
+};
+
+IdeSubprocessLauncher *ide_subprocess_launcher_new (GSubprocessFlags flags);
+const gchar *ide_subprocess_launcher_get_cwd (IdeSubprocessLauncher *self);
+void ide_subprocess_launcher_set_cwd (IdeSubprocessLauncher *self,
+ const gchar *cwd);
+GSubprocessFlags ide_subprocess_launcher_get_flags (IdeSubprocessLauncher *self);
+void ide_subprocess_launcher_set_flags (IdeSubprocessLauncher *self,
+ GSubprocessFlags flags);
+const gchar * const *ide_subprocess_launcher_get_environ (IdeSubprocessLauncher *self);
+void ide_subprocess_launcher_set_environ (IdeSubprocessLauncher *self,
+ const gchar * const *environ_);
+void ide_subprocess_launcher_setenv (IdeSubprocessLauncher *self,
+ const gchar *key,
+ const gchar *value,
+ gboolean replace);
+void ide_subprocess_launcher_overlay_environment (IdeSubprocessLauncher *self,
+ IdeEnvironment *environment);
+void ide_subprocess_launcher_push_args (IdeSubprocessLauncher *self,
+ const gchar * const *args);
+void ide_subprocess_launcher_push_argv (IdeSubprocessLauncher *self,
+ const gchar *argv);
+GSubprocess *ide_subprocess_launcher_spawn_sync (IdeSubprocessLauncher *self,
+ GCancellable *cancellable,
+ GError **error);
+void ide_subprocess_launcher_spawn_async (IdeSubprocessLauncher *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GSubprocess *ide_subprocess_launcher_spawn_finish (IdeSubprocessLauncher *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* IDE_SUBPROCESS_LAUNCHER_H */
diff --git a/libide/ide-types.h b/libide/ide-types.h
index 43ce39a..8f1b18e 100644
--- a/libide/ide-types.h
+++ b/libide/ide-types.h
@@ -41,6 +41,9 @@ typedef struct _IdeBuildResult IdeBuildResult;
typedef struct _IdeBuildSystem IdeBuildSystem;
+typedef struct _IdeConfiguration IdeConfiguration;
+typedef struct _IdeConfigurationManager IdeConfigurationManager;
+
typedef struct _IdeContext IdeContext;
typedef struct _IdeDebugger IdeDebugger;
@@ -49,9 +52,7 @@ typedef struct _IdeDebuggerInterface IdeDebuggerInterface;
typedef struct _IdeDeployer IdeDeployer;
typedef struct _IdeDevice IdeDevice;
-
typedef struct _IdeDeviceManager IdeDeviceManager;
-
typedef struct _IdeDeviceProvider IdeDeviceProvider;
typedef struct _IdeDiagnostic IdeDiagnostic;
@@ -62,6 +63,9 @@ typedef struct _IdeDiagnostician IdeDiagnostician;
typedef struct _IdeDiagnosticProvider IdeDiagnosticProvider;
+typedef struct _IdeEnvironment IdeEnvironment;
+typedef struct _IdeEnvironmentVariable IdeEnvironmentVariable;
+
typedef struct _IdeExecuter IdeExecuter;
typedef struct _IdeExecuterInterface IdeExecuterInterface;
@@ -98,6 +102,10 @@ typedef struct _IdeProjectFiles IdeProjectFiles;
typedef struct _IdeRefactory IdeRefactory;
typedef struct _IdeRefactoryInterface IdeRefactoryInterface;
+typedef struct _IdeRuntime IdeRuntime;
+typedef struct _IdeRuntimeManager IdeRuntimeManager;
+typedef struct _IdeRuntimeProvider IdeRuntimeProvider;
+
typedef struct _IdeScript IdeScript;
typedef struct _IdeScriptManager IdeScriptManager;
diff --git a/libide/ide.h b/libide/ide.h
index 7ec2f35..cfb7ffc 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -25,6 +25,8 @@ G_BEGIN_DECLS
#define IDE_INSIDE
+#include "ide-types.h"
+
#include "ide-application.h"
#include "ide-application-addin.h"
#include "ide-application-tool.h"
@@ -40,6 +42,8 @@ G_BEGIN_DECLS
#include "ide-completion-item.h"
#include "ide-completion-provider.h"
#include "ide-completion-results.h"
+#include "ide-configuration.h"
+#include "ide-configuration-manager.h"
#include "ide-context.h"
#include "ide-debug.h"
#include "ide-debugger.h"
@@ -52,6 +56,9 @@ G_BEGIN_DECLS
#include "ide-diagnostician.h"
#include "ide-diagnostic-provider.h"
#include "ide-enums.h"
+#include "ide-environment.h"
+#include "ide-environment-editor.h"
+#include "ide-environment-variable.h"
#include "ide-executable.h"
#include "ide-executer.h"
#include "ide-file.h"
@@ -80,6 +87,9 @@ G_BEGIN_DECLS
#include "ide-project-item.h"
#include "ide-recent-projects.h"
#include "ide-refactory.h"
+#include "ide-runtime.h"
+#include "ide-runtime-manager.h"
+#include "ide-runtime-provider.h"
#include "ide-script.h"
#include "ide-script-manager.h"
#include "ide-search-context.h"
@@ -97,6 +107,7 @@ G_BEGIN_DECLS
#include "ide-source-snippets-manager.h"
#include "ide-source-snippets.h"
#include "ide-source-view.h"
+#include "ide-subprocess-launcher.h"
#include "ide-symbol-resolver.h"
#include "ide-symbol.h"
#include "ide-target.h"
diff --git a/libide/local/ide-local-device.c b/libide/local/ide-local-device.c
index b80a22c..9314cbf 100644
--- a/libide/local/ide-local-device.c
+++ b/libide/local/ide-local-device.c
@@ -23,8 +23,7 @@
typedef struct
{
- GKeyFile *config;
- gchar *system_type;
+ gchar *system_type;
} IdeLocalDevicePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (IdeLocalDevice, ide_local_device, IDE_TYPE_DEVICE)
@@ -71,25 +70,12 @@ ide_local_device_get_system_type (IdeDevice *device)
return priv->system_type;
}
-static GKeyFile *
-ide_local_device_get_config (IdeDevice *device)
-{
- IdeLocalDevice *self = (IdeLocalDevice *)device;
- IdeLocalDevicePrivate *priv = ide_local_device_get_instance_private (self);
-
- g_return_val_if_fail (IDE_IS_LOCAL_DEVICE (device), NULL);
- g_return_val_if_fail (IDE_IS_LOCAL_DEVICE (self), NULL);
-
- return priv->config;
-}
-
static void
ide_local_device_finalize (GObject *object)
{
IdeLocalDevice *self = (IdeLocalDevice *)object;
IdeLocalDevicePrivate *priv = ide_local_device_get_instance_private (self);
- g_clear_pointer (&priv->config, g_key_file_unref);
g_clear_pointer (&priv->system_type, g_free);
G_OBJECT_CLASS (ide_local_device_parent_class)->finalize (object);
@@ -103,7 +89,6 @@ ide_local_device_class_init (IdeLocalDeviceClass *klass)
object_class->finalize = ide_local_device_finalize;
- device_class->get_config = ide_local_device_get_config;
device_class->get_system_type = ide_local_device_get_system_type;
}
@@ -113,9 +98,6 @@ ide_local_device_init (IdeLocalDevice *self)
IdeLocalDevicePrivate *priv = ide_local_device_get_instance_private (self);
priv->system_type = get_system_type ();
- priv->config = g_key_file_new ();
-
- g_key_file_set_string (priv->config, "autoconf", "--host", priv->system_type);
ide_device_set_display_name (IDE_DEVICE (self), g_get_host_name ());
ide_device_set_id (IDE_DEVICE (self), "local");
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index 6d9bc9e..46f7524 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -44,6 +44,7 @@
<file alias="ide-editor-frame.ui">../../data/ui/ide-editor-frame.ui</file>
<file alias="ide-editor-tweak-widget.ui">../../data/ui/ide-editor-tweak-widget.ui</file>
<file alias="ide-editor-view.ui">../../data/ui/ide-editor-view.ui</file>
+ <file alias="ide-environment-editor-row.ui">../../data/ui/ide-environment-editor-row.ui</file>
<file alias="ide-genesis-perspective.ui">../../data/ui/ide-genesis-perspective.ui</file>
<file alias="ide-greeter-perspective.ui">../../data/ui/ide-greeter-perspective.ui</file>
<file alias="ide-greeter-project-row.ui">../../data/ui/ide-greeter-project-row.ui</file>
diff --git a/libide/util/ide-posix.c b/libide/util/ide-posix.c
new file mode 100644
index 0000000..f15ca39
--- /dev/null
+++ b/libide/util/ide-posix.c
@@ -0,0 +1,37 @@
+/* ide-posix.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <sys/utsname.h>
+
+#include "ide-posix.h"
+
+gchar *
+ide_get_system_arch (void)
+{
+ struct utsname u;
+ const char *machine;
+
+ if (uname (&u) < 0)
+ return g_strdup ("unknown");
+
+ /* config.sub doesn't accept amd64-OS */
+ machine = strcmp (u.machine, "amd64") ? u.machine : "x86_64";
+
+ return g_strdup (machine);
+}
diff --git a/libide/util/ide-posix.h b/libide/util/ide-posix.h
new file mode 100644
index 0000000..193d60f
--- /dev/null
+++ b/libide/util/ide-posix.h
@@ -0,0 +1,30 @@
+/* ide-posix.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_POSIX_H
+#define IDE_POSIX_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gchar *ide_get_system_arch (void);
+
+G_END_DECLS
+
+#endif /* IDE_POSIX_H */
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index b316c78..22e70c4 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -27,6 +27,7 @@ SUBDIRS = \
terminal \
todo \
vala-pack \
+ xdg-app \
xml-pack \
$(NULL)
diff --git a/plugins/autotools/ide-autotools-build-system.c b/plugins/autotools/ide-autotools-build-system.c
index 3df3824..3b43fa5 100644
--- a/plugins/autotools/ide-autotools-build-system.c
+++ b/plugins/autotools/ide-autotools-build-system.c
@@ -32,6 +32,8 @@
#include "ide-autotools-build-system.h"
#include "ide-autotools-builder.h"
#include "ide-buffer-manager.h"
+#include "ide-configuration.h"
+#include "ide-configuration-manager.h"
#include "ide-context.h"
#include "ide-debug.h"
#include "ide-device.h"
@@ -39,6 +41,8 @@
#include "ide-file.h"
#include "ide-internal.h"
#include "ide-makecache.h"
+#include "ide-runtime.h"
+#include "ide-runtime-manager.h"
#include "ide-tags-builder.h"
#define MAKECACHE_KEY "makecache"
@@ -84,24 +88,21 @@ ide_autotools_build_system_get_tarball_name (IdeAutotoolsBuildSystem *self)
}
static IdeBuilder *
-ide_autotools_build_system_get_builder (IdeBuildSystem *build_system,
- GKeyFile *config,
- IdeDevice *device,
- GError **error)
+ide_autotools_build_system_get_builder (IdeBuildSystem *build_system,
+ IdeConfiguration *configuration,
+ GError **error)
{
IdeBuilder *ret;
IdeContext *context;
- g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_SYSTEM (build_system), NULL);
- g_return_val_if_fail (config, NULL);
- g_return_val_if_fail (IDE_IS_DEVICE (device), NULL);
+ g_assert (IDE_IS_AUTOTOOLS_BUILD_SYSTEM (build_system));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
context = ide_object_get_context (IDE_OBJECT (build_system));
ret = g_object_new (IDE_TYPE_AUTOTOOLS_BUILDER,
"context", context,
- "config", config,
- "device", device,
+ "configuration", configuration,
NULL);
return ret;
@@ -213,43 +214,15 @@ ide_autotools_build_system_discover_file_finish (IdeAutotoolsBuildSystem *syste
}
static void
-ide_autotools_build_system__bootstrap_cb (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
-{
- IdeAutotoolsBuilder *builder = (IdeAutotoolsBuilder *)object;
- g_autoptr(GTask) task = user_data;
- g_autoptr(GFile) build_directory = NULL;
- g_autoptr(GFile) makefile = NULL;
- GError *error = NULL;
-
- g_assert (IDE_IS_AUTOTOOLS_BUILDER (builder));
- g_assert (G_IS_TASK (task));
-
- if (!ide_autotools_builder_bootstrap_finish (builder, result, &error))
- {
- g_task_return_error (task, error);
- return;
- }
-
- build_directory = ide_autotools_builder_get_build_directory (builder);
- makefile = g_file_get_child (build_directory, "Makefile");
-
- g_task_return_pointer (task, g_object_ref (makefile), g_object_unref);
-}
-
-static void
ide_autotools_build_system_get_local_makefile_async (IdeAutotoolsBuildSystem *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
IdeContext *context;
- IdeDeviceManager *device_manager;
- IdeDevice *device;
+ g_autoptr(IdeConfiguration) configuration = NULL;
g_autoptr(GTask) task = NULL;
g_autoptr(IdeBuilder) builder = NULL;
- g_autoptr(GKeyFile) config = NULL;
g_autoptr(GFile) build_directory = NULL;
g_autoptr(GFile) makefile = NULL;
GError *error = NULL;
@@ -260,10 +233,10 @@ ide_autotools_build_system_get_local_makefile_async (IdeAutotoolsBuildSystem *se
task = g_task_new (self, cancellable, callback, user_data);
context = ide_object_get_context (IDE_OBJECT (self));
- device_manager = ide_context_get_device_manager (context);
- device = ide_device_manager_get_device (device_manager, "local");
- config = g_key_file_new ();
- builder = ide_autotools_build_system_get_builder (IDE_BUILD_SYSTEM (self), config, device, &error);
+
+ configuration = ide_configuration_new (context, "autotools-bootstrap", "local", "host");
+
+ builder = ide_autotools_build_system_get_builder (IDE_BUILD_SYSTEM (self), configuration, &error);
if (builder == NULL)
{
@@ -271,18 +244,6 @@ ide_autotools_build_system_get_local_makefile_async (IdeAutotoolsBuildSystem *se
return;
}
- /*
- * If we haven't yet bootstrapped the project, let's go ahead and do that now.
- */
- if (ide_autotools_builder_get_needs_bootstrap (IDE_AUTOTOOLS_BUILDER (builder)))
- {
- ide_autotools_builder_bootstrap_async (IDE_AUTOTOOLS_BUILDER (builder),
- cancellable,
- ide_autotools_build_system__bootstrap_cb,
- g_object_ref (task));
- return;
- }
-
build_directory = ide_autotools_builder_get_build_directory (IDE_AUTOTOOLS_BUILDER (builder));
makefile = g_file_get_child (build_directory, "Makefile");
@@ -862,17 +823,23 @@ simple_make_command_cb (GObject *object,
}
static void
-simple_make_command (GFile *directory,
- const gchar *target,
- GTask *task)
+simple_make_command (GFile *directory,
+ const gchar *target,
+ GTask *task,
+ IdeConfiguration *configuration)
{
- g_autoptr(GSubprocessLauncher) launcher = NULL;
+ g_autoptr(IdeSubprocessLauncher) launcher = NULL;
g_autoptr(GSubprocess) subprocess = NULL;
+ GCancellable *cancellable;
+ IdeRuntime *runtime;
GError *error = NULL;
g_assert (G_IS_FILE (directory));
g_assert (target != NULL);
g_assert (G_IS_TASK (task));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ cancellable = g_task_get_cancellable (task);
if (!g_file_is_native (directory))
{
@@ -883,12 +850,29 @@ simple_make_command (GFile *directory,
return;
}
- launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDERR_SILENCE |
- G_SUBPROCESS_FLAGS_STDOUT_SILENCE);
- g_subprocess_launcher_set_cwd (launcher, g_file_get_path (directory));
+ if (NULL == (runtime = ide_configuration_get_runtime (configuration)))
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND,
+ "Failed to locate runtime");
+ return;
+ }
+
+ if (NULL == (launcher = ide_runtime_create_launcher (runtime, &error)))
+ {
+ g_task_return_error (task, error);
+ return;
+ }
+
+ ide_subprocess_launcher_set_cwd (launcher, g_file_get_path (directory));
- subprocess = g_subprocess_launcher_spawn (launcher, &error, GNU_MAKE_NAME, target, NULL);
- if (subprocess == NULL)
+ if (ide_runtime_contains_program_in_path (runtime, "gmake", cancellable))
+ ide_subprocess_launcher_push_argv (launcher, "gmake");
+ else
+ ide_subprocess_launcher_push_argv (launcher, "make");
+
+ if (NULL == (subprocess = ide_subprocess_launcher_spawn_sync (launcher, cancellable, &error)))
{
g_task_return_error (task, error);
return;
@@ -901,28 +885,35 @@ simple_make_command (GFile *directory,
}
static void
-ide_autotools_build_system_build_async (IdeTagsBuilder *builder,
- GFile *file_or_directory,
- gboolean recursive,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
+ide_autotools_build_system_tags_build_async (IdeTagsBuilder *builder,
+ GFile *file_or_directory,
+ gboolean recursive,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
IdeAutotoolsBuildSystem *self = (IdeAutotoolsBuildSystem *)builder;
+ IdeConfigurationManager *config_manager;
+ IdeConfiguration *configuration;
+ IdeContext *context;
g_autoptr(GTask) task = NULL;
- g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_SYSTEM (self));
- g_return_if_fail (G_IS_FILE (file_or_directory));
- g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_assert (IDE_IS_AUTOTOOLS_BUILD_SYSTEM (self));
+ g_assert (G_IS_FILE (file_or_directory));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ config_manager = ide_context_get_configuration_manager (context);
+ configuration = ide_configuration_manager_get_current (config_manager);
task = g_task_new (self, cancellable, callback, user_data);
- simple_make_command (file_or_directory, "ctags", task);
+ simple_make_command (file_or_directory, "ctags", task, configuration);
}
static gboolean
-ide_autotools_build_system_build_finish (IdeTagsBuilder *builder,
- GAsyncResult *result,
- GError **error)
+ide_autotools_build_system_tags_build_finish (IdeTagsBuilder *builder,
+ GAsyncResult *result,
+ GError **error)
{
IdeAutotoolsBuildSystem *self = (IdeAutotoolsBuildSystem *)builder;
GTask *task = (GTask *)result;
@@ -936,6 +927,6 @@ ide_autotools_build_system_build_finish (IdeTagsBuilder *builder,
static void
tags_builder_iface_init (IdeTagsBuilderInterface *iface)
{
- iface->build_async = ide_autotools_build_system_build_async;
- iface->build_finish = ide_autotools_build_system_build_finish;
+ iface->build_async = ide_autotools_build_system_tags_build_async;
+ iface->build_finish = ide_autotools_build_system_tags_build_finish;
}
diff --git a/plugins/autotools/ide-autotools-build-task.c b/plugins/autotools/ide-autotools-build-task.c
index b84fe78..c476643 100644
--- a/plugins/autotools/ide-autotools-build-task.c
+++ b/plugins/autotools/ide-autotools-build-task.c
@@ -22,34 +22,35 @@
#include <fcntl.h>
#include <glib/gi18n.h>
+#include <ide.h>
+#include <stdlib.h>
#include <unistd.h>
#include "ide-autotools-build-task.h"
-#include "ide-context.h"
-#include "ide-device.h"
-#include "ide-project.h"
-typedef struct
+struct _IdeAutotoolsBuildTask
{
- GKeyFile *config;
- IdeDevice *device;
- GFile *directory;
- guint require_autogen : 1;
- guint require_configure : 1;
- guint executed : 1;
-} IdeAutotoolsBuildTaskPrivate;
+ IdeObject parent_instance;
+ IdeConfiguration *configuration;
+ GFile *directory;
+ GPtrArray *extra_targets;
+ guint require_autogen : 1;
+ guint require_configure : 1;
+ guint executed : 1;
+};
typedef struct
{
- gchar *directory_path;
- gchar *project_path;
- gchar *parallel;
- gchar *system_type;
- gchar **configure_argv;
- gchar **make_targets;
- guint require_autogen : 1;
- guint require_configure : 1;
- guint bootstrap_only : 1;
+ gchar *directory_path;
+ gchar *project_path;
+ gchar *parallel;
+ gchar *system_type;
+ gchar **configure_argv;
+ gchar **make_targets;
+ IdeRuntime *runtime;
+ guint require_autogen : 1;
+ guint require_configure : 1;
+ guint bootstrap_only : 1;
} WorkerState;
typedef gboolean (*WorkStep) (GTask *task,
@@ -57,40 +58,40 @@ typedef gboolean (*WorkStep) (GTask *task,
WorkerState *state,
GCancellable *cancellable);
-G_DEFINE_TYPE_WITH_PRIVATE (IdeAutotoolsBuildTask, ide_autotools_build_task,
- IDE_TYPE_BUILD_RESULT)
+G_DEFINE_TYPE (IdeAutotoolsBuildTask, ide_autotools_build_task, IDE_TYPE_BUILD_RESULT)
enum {
PROP_0,
- PROP_CONFIG,
- PROP_DEVICE,
+ PROP_CONFIGURATION,
PROP_DIRECTORY,
PROP_REQUIRE_AUTOGEN,
PROP_REQUIRE_CONFIGURE,
LAST_PROP
};
-static GSubprocess *log_and_spawn (IdeAutotoolsBuildTask *self,
- GSubprocessLauncher *launcher,
- GError **error,
- const gchar *argv0,
- ...) G_GNUC_NULL_TERMINATED;
-static gboolean step_mkdirs (GTask *task,
- IdeAutotoolsBuildTask *self,
- WorkerState *state,
- GCancellable *cancellable);
-static gboolean step_autogen (GTask *task,
- IdeAutotoolsBuildTask *self,
- WorkerState *state,
- GCancellable *cancellable);
-static gboolean step_configure (GTask *task,
- IdeAutotoolsBuildTask *self,
- WorkerState *state,
- GCancellable *cancellable);
-static gboolean step_make_all (GTask *task,
- IdeAutotoolsBuildTask *self,
- WorkerState *state,
- GCancellable *cancellable);
+static GSubprocess *log_and_spawn (IdeAutotoolsBuildTask *self,
+ IdeSubprocessLauncher *launcher,
+ GError **error,
+ const gchar *argv0,
+ ...) G_GNUC_NULL_TERMINATED;
+static gboolean step_mkdirs (GTask *task,
+ IdeAutotoolsBuildTask *self,
+ WorkerState *state,
+ GCancellable *cancellable);
+static gboolean step_autogen (GTask *task,
+ IdeAutotoolsBuildTask *self,
+ WorkerState *state,
+ GCancellable *cancellable);
+static gboolean step_configure (GTask *task,
+ IdeAutotoolsBuildTask *self,
+ WorkerState *state,
+ GCancellable *cancellable);
+static gboolean step_make_all (GTask *task,
+ IdeAutotoolsBuildTask *self,
+ WorkerState *state,
+ GCancellable *cancellable);
+static void apply_environment (IdeAutotoolsBuildTask *self,
+ IdeSubprocessLauncher *launcher);
static GParamSpec *properties [LAST_PROP];
static WorkStep workSteps [] = {
@@ -102,128 +103,37 @@ static WorkStep workSteps [] = {
};
gboolean
-ide_autotools_build_task_get_require_autogen (IdeAutotoolsBuildTask *task)
+ide_autotools_build_task_get_require_autogen (IdeAutotoolsBuildTask *self)
{
- IdeAutotoolsBuildTaskPrivate *priv;
-
- g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (task), FALSE);
-
- priv = ide_autotools_build_task_get_instance_private (task);
+ g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), FALSE);
- return priv->require_autogen;
+ return self->require_autogen;
}
static void
-ide_autotools_build_task_set_require_autogen (IdeAutotoolsBuildTask *task,
+ide_autotools_build_task_set_require_autogen (IdeAutotoolsBuildTask *self,
gboolean require_autogen)
{
- IdeAutotoolsBuildTaskPrivate *priv;
-
- g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (task));
-
- priv = ide_autotools_build_task_get_instance_private (task);
+ g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
- priv->require_autogen = !!require_autogen;
+ self->require_autogen = !!require_autogen;
}
gboolean
-ide_autotools_build_task_get_require_configure (IdeAutotoolsBuildTask *task)
+ide_autotools_build_task_get_require_configure (IdeAutotoolsBuildTask *self)
{
- IdeAutotoolsBuildTaskPrivate *priv;
-
- g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (task), FALSE);
-
- priv = ide_autotools_build_task_get_instance_private (task);
+ g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), FALSE);
- return priv->require_configure;
+ return self->require_configure;
}
static void
-ide_autotools_build_task_set_require_configure (IdeAutotoolsBuildTask *task,
+ide_autotools_build_task_set_require_configure (IdeAutotoolsBuildTask *self,
gboolean require_configure)
{
- IdeAutotoolsBuildTaskPrivate *priv;
-
- g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (task));
-
- priv = ide_autotools_build_task_get_instance_private (task);
-
- priv->require_autogen = !!require_configure;
-}
-
-/**
- * ide_autotools_build_task_get_config:
- * @self: A #IdeAutotoolsBuildTask.
- *
- * Gets the "config" property of the task. This is the overlay config to be
- * applied on top of the device config when compiling.
- *
- * Returns: (transfer none) (nullable): A #GKeyFile or %NULL.
- */
-GKeyFile *
-ide_autotools_build_task_get_config (IdeAutotoolsBuildTask *self)
-{
- IdeAutotoolsBuildTaskPrivate *priv;
-
- g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
-
- priv = ide_autotools_build_task_get_instance_private (self);
-
- return priv->config;
-}
-
-static void
-ide_autotools_build_task_set_config (IdeAutotoolsBuildTask *self,
- GKeyFile *config)
-{
- IdeAutotoolsBuildTaskPrivate *priv;
-
- g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
-
- priv = ide_autotools_build_task_get_instance_private (self);
-
- if (priv->config != config)
- {
- g_clear_pointer (&priv->config, g_key_file_unref);
- priv->config = config ? g_key_file_ref (config) : NULL;
- g_object_notify_by_pspec (G_OBJECT (self),
- properties [PROP_CONFIG]);
- }
-}
-
-/**
- * ide_autotools_build_task_get_device:
- * @self: A #IdeAutotoolsBuildTask.
- *
- * Gets the "device" property. This is the device we are compiling for,
- * which may involve cross-compiling.
- *
- * Returns: (transfer none): An #IdeDevice.
- */
-IdeDevice *
-ide_autotools_build_task_get_device (IdeAutotoolsBuildTask *self)
-{
- IdeAutotoolsBuildTaskPrivate *priv;
-
- g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
-
- priv = ide_autotools_build_task_get_instance_private (self);
-
- return priv->device;
-}
-
-static void
-ide_autotools_build_task_set_device (IdeAutotoolsBuildTask *self,
- IdeDevice *device)
-{
- IdeAutotoolsBuildTaskPrivate *priv;
-
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
- priv = ide_autotools_build_task_get_instance_private (self);
-
- if (g_set_object (&priv->device, device))
- g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEVICE]);
+ self->require_autogen = !!require_configure;
}
/**
@@ -236,26 +146,18 @@ ide_autotools_build_task_set_device (IdeAutotoolsBuildTask *self,
GFile *
ide_autotools_build_task_get_directory (IdeAutotoolsBuildTask *self)
{
- IdeAutotoolsBuildTaskPrivate *priv;
-
g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
- priv = ide_autotools_build_task_get_instance_private (self);
-
- return priv->directory;
+ return self->directory;
}
static void
ide_autotools_build_task_set_directory (IdeAutotoolsBuildTask *self,
GFile *directory)
{
- IdeAutotoolsBuildTaskPrivate *priv;
-
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
g_return_if_fail (!directory || G_IS_FILE (directory));
- priv = ide_autotools_build_task_get_instance_private (self);
-
/*
* We require a build directory that is accessable via a native path.
*/
@@ -272,23 +174,44 @@ ide_autotools_build_task_set_directory (IdeAutotoolsBuildTask *self,
}
}
- if (priv->directory != directory)
- if (g_set_object (&priv->directory, directory))
+ if (self->directory != directory)
+ if (g_set_object (&self->directory, directory))
g_object_notify_by_pspec (G_OBJECT (self),
properties [PROP_DIRECTORY]);
}
+/**
+ * ide_autotools_build_task_get_configuration:
+ * @self: An #IdeAutotoolsBuildTask
+ *
+ * Gets the configuration to use for the build.
+ */
+IdeConfiguration *
+ide_autotools_build_task_get_configuration (IdeAutotoolsBuildTask *self)
+{
+ g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
+
+ return self->configuration;
+}
+
+static void
+ide_autotools_build_task_set_configuration (IdeAutotoolsBuildTask *self,
+ IdeConfiguration *configuration)
+{
+ g_assert (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ if (g_set_object (&self->configuration, configuration))
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONFIGURATION]);
+}
+
static void
ide_autotools_build_task_finalize (GObject *object)
{
IdeAutotoolsBuildTask *self = (IdeAutotoolsBuildTask *)object;
- IdeAutotoolsBuildTaskPrivate *priv;
-
- priv = ide_autotools_build_task_get_instance_private (self);
- g_clear_object (&priv->device);
- g_clear_object (&priv->directory);
- g_clear_pointer (&priv->config, g_key_file_unref);
+ g_clear_object (&self->directory);
+ g_clear_object (&self->configuration);
G_OBJECT_CLASS (ide_autotools_build_task_parent_class)->finalize (object);
}
@@ -303,12 +226,8 @@ ide_autotools_build_task_get_property (GObject *object,
switch (prop_id)
{
- case PROP_CONFIG:
- g_value_set_object (value, ide_autotools_build_task_get_config (self));
- break;
-
- case PROP_DEVICE:
- g_value_set_object (value, ide_autotools_build_task_get_device (self));
+ case PROP_CONFIGURATION:
+ g_value_set_object (value, ide_autotools_build_task_get_configuration (self));
break;
case PROP_DIRECTORY:
@@ -338,12 +257,8 @@ ide_autotools_build_task_set_property (GObject *object,
switch (prop_id)
{
- case PROP_CONFIG:
- ide_autotools_build_task_set_config (self, g_value_get_boxed (value));
- break;
-
- case PROP_DEVICE:
- ide_autotools_build_task_set_device (self, g_value_get_object (value));
+ case PROP_CONFIGURATION:
+ ide_autotools_build_task_set_configuration (self, g_value_get_object (value));
break;
case PROP_DIRECTORY:
@@ -372,24 +287,15 @@ ide_autotools_build_task_class_init (IdeAutotoolsBuildTaskClass *klass)
object_class->get_property = ide_autotools_build_task_get_property;
object_class->set_property = ide_autotools_build_task_set_property;
- properties [PROP_CONFIG] =
- g_param_spec_boxed ("config",
- "Config",
- "The overlay config for the compilation.",
- G_TYPE_KEY_FILE,
+ properties [PROP_CONFIGURATION] =
+ g_param_spec_object ("configuration",
+ "Configuration",
+ "The configuration for this build.",
+ IDE_TYPE_CONFIGURATION,
(G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
- properties [PROP_DEVICE] =
- g_param_spec_object ("device",
- "Device",
- "The device to build for.",
- IDE_TYPE_DEVICE,
- (G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_STATIC_STRINGS));
-
properties [PROP_DIRECTORY] =
g_param_spec_object ("directory",
"Directory",
@@ -429,97 +335,65 @@ static gchar **
gen_configure_argv (IdeAutotoolsBuildTask *self,
WorkerState *state)
{
- IdeAutotoolsBuildTaskPrivate *priv;
IdeDevice *device;
- const gchar *system_type;
- GKeyFile *configs[2];
GPtrArray *ar;
- GHashTable *ht;
- gpointer k, v;
- GHashTableIter iter;
+ const gchar *opts;
+ const gchar *system_type;
+ gchar *prefix;
gchar *configure_path;
- guint j;
-
- g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
- priv = ide_autotools_build_task_get_instance_private (self);
-
- ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-
- configs [0] = ide_device_get_config (priv->device);
- configs [1] = priv->config;
-
- for (j = 0; j < G_N_ELEMENTS (configs); j++)
- {
- GKeyFile *config = configs [j];
+ g_assert (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
+ g_assert (state != NULL);
- if (config)
- {
- if (g_key_file_has_group (config, "autoconf"))
- {
- gchar **keys;
- gsize len;
- gsize i;
-
- keys = g_key_file_get_keys (config, "autoconf", &len, NULL);
-
- for (i = 0; i < len; i++)
- {
- gchar *value;
-
- if (*keys [i] == '-')
- {
- value = g_key_file_get_string (config,
- "autoconf", keys [i],
- NULL);
- if (value)
- g_hash_table_replace (ht, g_strdup (keys [i]), value);
- }
- }
-
- g_strfreev (keys);
- }
- }
- }
+ ar = g_ptr_array_new_with_free_func (g_free);
- ar = g_ptr_array_new ();
+ /* ./configure */
configure_path = g_build_filename (state->project_path, "configure", NULL);
g_ptr_array_add (ar, configure_path);
- g_hash_table_iter_init (&iter, ht);
+ /* --prefix /app */
+ if (NULL == (prefix = g_strdup (ide_configuration_get_prefix (self->configuration))))
+ prefix = g_build_filename (state->project_path, "_install", NULL);
+ g_ptr_array_add (ar, g_strdup_printf ("--prefix=%s", prefix));
+ g_free (prefix);
- while (g_hash_table_iter_next (&iter, &k, &v))
- {
- g_ptr_array_add (ar, g_strdup (k));
- if (v && *(gchar *)v)
- g_ptr_array_add (ar, g_strdup (v));
- }
+ /* --host=triplet */
+ device = ide_configuration_get_device (self->configuration);
+ system_type = ide_device_get_system_type (device);
+ g_ptr_array_add (ar, g_strdup_printf ("--host=%s", system_type));
- if (!g_hash_table_lookup (ht, "--prefix"))
+ if (NULL != (opts = ide_configuration_get_config_opts (self->configuration)))
{
- gchar *prefix;
+ GError *error = NULL;
+ gint argc;
+ gchar **argv;
- prefix = g_build_filename (state->project_path, "_install", NULL);
- g_ptr_array_add (ar, g_strdup_printf ("--prefix=%s", prefix));
- g_free (prefix);
+ if (g_shell_parse_argv (opts, &argc, &argv, &error))
+ {
+ for (guint i = 0; i < argc; i++)
+ g_ptr_array_add (ar, argv [i]);
+ g_free (argv);
+ }
+ else
+ {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
}
- device = ide_autotools_build_task_get_device (self);
- system_type = ide_device_get_system_type (device);
- g_ptr_array_add (ar, g_strdup_printf ("--host=%s", system_type));
-
g_ptr_array_add (ar, NULL);
- g_hash_table_unref (ht);
return (gchar **)g_ptr_array_free (ar, FALSE);
}
static WorkerState *
-worker_state_new (IdeAutotoolsBuildTask *self)
+worker_state_new (IdeAutotoolsBuildTask *self,
+ IdeBuilderBuildFlags flags)
{
- IdeAutotoolsBuildTaskPrivate *priv;
g_autofree gchar *name = NULL;
IdeContext *context;
+ IdeDevice *device;
+ IdeRuntime *runtime;
GPtrArray *make_targets;
GFile *project_dir;
GFile *project_file;
@@ -527,12 +401,14 @@ worker_state_new (IdeAutotoolsBuildTask *self)
gint val32;
g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
-
- priv = ide_autotools_build_task_get_instance_private (self);
+ g_return_val_if_fail (IDE_IS_CONFIGURATION (self->configuration), NULL);
context = ide_object_get_context (IDE_OBJECT (self));
project_file = ide_context_get_project_file (context);
+ device = ide_configuration_get_device (self->configuration);
+ runtime = ide_configuration_get_runtime (self->configuration);
+
name = g_file_get_basename (project_file);
if (g_str_has_prefix (name, "configure."))
@@ -541,13 +417,14 @@ worker_state_new (IdeAutotoolsBuildTask *self)
project_dir = g_object_ref (project_file);
state = g_slice_new0 (WorkerState);
- state->require_autogen = priv->require_autogen;
- state->require_configure = priv->require_configure;
- state->directory_path = g_file_get_path (priv->directory);
+ state->require_autogen = self->require_autogen || !!(flags & IDE_BUILDER_BUILD_FLAGS_FORCE_BOOTSTRAP);
+ state->require_configure = self->require_configure || (state->require_autogen && !(flags &
IDE_BUILDER_BUILD_FLAGS_NO_CONFIGURE));
+ state->directory_path = g_file_get_path (self->directory);
state->project_path = g_file_get_path (project_dir);
- state->system_type = g_strdup (ide_device_get_system_type (priv->device));
+ state->system_type = g_strdup (ide_device_get_system_type (device));
+ state->runtime = g_object_ref (runtime);
- val32 = g_key_file_get_integer (priv->config, "parallel", "workers", NULL);
+ val32 = atoi (ide_configuration_getenv (self->configuration, "PARALLEL") ?: "-1");
if (val32 == -1)
state->parallel = g_strdup_printf ("-j%u", g_get_num_processors () + 1);
@@ -558,22 +435,21 @@ worker_state_new (IdeAutotoolsBuildTask *self)
make_targets = g_ptr_array_new ();
- if (priv->config && g_key_file_get_boolean (priv->config, "autotools", "rebuild", NULL))
+ if (0 != (flags & IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN))
{
state->require_autogen = TRUE;
state->require_configure = TRUE;
g_ptr_array_add (make_targets, g_strdup ("clean"));
}
- if (priv->config && g_key_file_get_boolean (priv->config, "autotools", "clean-only", NULL))
- g_ptr_array_add (make_targets, g_strdup ("clean"));
- else
+ if (0 == (flags & IDE_BUILDER_BUILD_FLAGS_NO_BUILD))
g_ptr_array_add (make_targets, g_strdup ("all"));
g_ptr_array_add (make_targets, NULL);
+
state->make_targets = (gchar **)g_ptr_array_free (make_targets, FALSE);
- if (g_key_file_get_boolean (priv->config, "autotools", "bootstrap-only", NULL))
+ if (0 != (flags & IDE_BUILDER_BUILD_FLAGS_NO_CONFIGURE))
{
state->require_autogen = TRUE;
state->require_configure = TRUE;
@@ -597,6 +473,7 @@ worker_state_free (void *data)
g_free (state->parallel);
g_strfreev (state->configure_argv);
g_strfreev (state->make_targets);
+ g_clear_object (&state->runtime);
g_slice_free (WorkerState, state);
}
@@ -625,22 +502,41 @@ ide_autotools_build_task_execute_worker (GTask *task,
g_task_return_boolean (task, TRUE);
}
+static void
+ide_autotools_build_task_prebuild_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeRuntime *runtime = (IdeRuntime *)object;
+ g_autoptr(GTask) task = user_data;
+ GError *error = NULL;
+
+ g_assert (IDE_IS_RUNTIME (runtime));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ if (!ide_runtime_prebuild_finish (runtime, result, &error))
+ {
+ g_task_return_error (task, error);
+ return;
+ }
+
+ g_task_run_in_thread (task, ide_autotools_build_task_execute_worker);
+}
+
void
ide_autotools_build_task_execute_async (IdeAutotoolsBuildTask *self,
+ IdeBuilderBuildFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
- IdeAutotoolsBuildTaskPrivate *priv;
g_autoptr(GTask) task = NULL;
WorkerState *state;
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
- priv = ide_autotools_build_task_get_instance_private (self);
-
- if (priv->executed)
+ if (self->executed)
{
g_task_report_new_error (self, callback, user_data,
ide_autotools_build_task_execute_async,
@@ -650,13 +546,20 @@ ide_autotools_build_task_execute_async (IdeAutotoolsBuildTask *self,
return;
}
- priv->executed = TRUE;
+ self->executed = TRUE;
- state = worker_state_new (self);
+ state = worker_state_new (self, flags);
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_task_data (task, state, worker_state_free);
- g_task_run_in_thread (task, ide_autotools_build_task_execute_worker);
+
+ /*
+ * Execute the pre-hook for the runtime before we start building.
+ */
+ ide_runtime_prebuild_async (state->runtime,
+ cancellable,
+ ide_autotools_build_task_prebuild_cb,
+ g_object_ref (task));
}
gboolean
@@ -675,40 +578,30 @@ ide_autotools_build_task_execute_finish (IdeAutotoolsBuildTask *self,
static GSubprocess *
log_and_spawn (IdeAutotoolsBuildTask *self,
- GSubprocessLauncher *launcher,
+ IdeSubprocessLauncher *launcher,
GError **error,
const gchar *argv0,
...)
{
GSubprocess *ret;
- GPtrArray *argv;
GString *log;
gchar *item;
va_list args;
- log = g_string_new (NULL);
- g_string_append (log, argv0);
-
- argv = g_ptr_array_new ();
- g_ptr_array_add (argv, (gchar *)argv0);
+ log = g_string_new (argv0);
+ ide_subprocess_launcher_push_argv (launcher, argv0);
va_start (args, argv0);
- while ((item = va_arg (args, gchar *)))
+ while (NULL != (item = va_arg (args, gchar *)))
{
- g_ptr_array_add (argv, item);
+ ide_subprocess_launcher_push_argv (launcher, item);
g_string_append_printf (log, " '%s'", item);
}
va_end (args);
- g_ptr_array_add (argv, NULL);
-
ide_build_result_log_stdout (IDE_BUILD_RESULT (self), "%s", log->str);
- ret = g_subprocess_launcher_spawnv (launcher,
- (const gchar * const *)argv->pdata,
- error);
-
+ ret = ide_subprocess_launcher_spawn_sync (launcher, NULL, error);
g_string_free (log, TRUE);
- g_ptr_array_unref (argv);
return ret;
}
@@ -756,7 +649,7 @@ step_autogen (GTask *task,
{
g_autofree gchar *autogen_sh_path = NULL;
g_autofree gchar *configure_path = NULL;
- g_autoptr(GSubprocessLauncher) launcher = NULL;
+ g_autoptr(IdeSubprocessLauncher) launcher = NULL;
g_autoptr(GSubprocess) process = NULL;
GError *error = NULL;
@@ -793,13 +686,18 @@ step_autogen (GTask *task,
return FALSE;
}
- ide_build_result_set_mode (IDE_BUILD_RESULT (self), _("Autogening…"));
+ ide_build_result_set_mode (IDE_BUILD_RESULT (self), _("Running autogen…"));
+
+ if (NULL == (launcher = ide_runtime_create_launcher (state->runtime, &error)))
+ {
+ g_task_return_error (task, error);
+ return FALSE;
+ }
- launcher = g_subprocess_launcher_new ((G_SUBPROCESS_FLAGS_STDOUT_PIPE |
- G_SUBPROCESS_FLAGS_STDERR_PIPE));
- g_subprocess_launcher_set_cwd (launcher, state->project_path);
- g_subprocess_launcher_setenv (launcher, "LANG", "C", TRUE);
- g_subprocess_launcher_setenv (launcher, "NOCONFIGURE", "1", TRUE);
+ ide_subprocess_launcher_set_cwd (launcher, state->project_path);
+ ide_subprocess_launcher_setenv (launcher, "LANG", "C", TRUE);
+ ide_subprocess_launcher_setenv (launcher, "NOCONFIGURE", "1", TRUE);
+ apply_environment (self, launcher);
process = log_and_spawn (self, launcher, &error, autogen_sh_path, NULL);
@@ -836,7 +734,7 @@ step_configure (GTask *task,
WorkerState *state,
GCancellable *cancellable)
{
- g_autoptr(GSubprocessLauncher) launcher = NULL;
+ g_autoptr(IdeSubprocessLauncher) launcher = NULL;
g_autoptr(GSubprocess) process = NULL;
g_autofree gchar *makefile_path = NULL;
g_autofree gchar *config_log = NULL;
@@ -857,22 +755,23 @@ step_configure (GTask *task,
return TRUE;
}
- ide_build_result_set_mode (IDE_BUILD_RESULT (self), _("Configuring…"));
+ ide_build_result_set_mode (IDE_BUILD_RESULT (self), _("Running configure…"));
+
+ if (NULL == (launcher = ide_runtime_create_launcher (state->runtime, &error)))
+ return FALSE;
- launcher = g_subprocess_launcher_new ((G_SUBPROCESS_FLAGS_STDERR_PIPE |
- G_SUBPROCESS_FLAGS_STDOUT_PIPE));
- g_subprocess_launcher_set_cwd (launcher, state->directory_path);
- g_subprocess_launcher_setenv (launcher, "LANG", "C", TRUE);
+ ide_subprocess_launcher_set_flags (launcher,
+ (G_SUBPROCESS_FLAGS_STDERR_PIPE |
+ G_SUBPROCESS_FLAGS_STDOUT_PIPE));
+ ide_subprocess_launcher_set_cwd (launcher, state->directory_path);
+ ide_subprocess_launcher_setenv (launcher, "LANG", "C", TRUE);
+ apply_environment (self, launcher);
config_log = g_strjoinv (" ", state->configure_argv);
ide_build_result_log_stdout (IDE_BUILD_RESULT (self), "%s", config_log);
+ ide_subprocess_launcher_push_args (launcher, (const gchar * const *)state->configure_argv);
- process = g_subprocess_launcher_spawnv (
- launcher,
- (const gchar * const *)state->configure_argv,
- &error);
-
- if (!process)
+ if (NULL == (process = ide_subprocess_launcher_spawn_sync (launcher, cancellable, &error)))
{
g_task_return_error (task, error);
return FALSE;
@@ -901,9 +800,10 @@ step_make_all (GTask *task,
WorkerState *state,
GCancellable *cancellable)
{
- g_autoptr(GSubprocessLauncher) launcher = NULL;
+ g_autoptr(IdeSubprocessLauncher) launcher = NULL;
g_autoptr(GSubprocess) process = NULL;
const gchar * const *targets;
+ const gchar *make = NULL;
gchar *default_targets[] = { "all", NULL };
GError *error = NULL;
guint i;
@@ -913,10 +813,34 @@ step_make_all (GTask *task,
g_assert (state);
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
- launcher = g_subprocess_launcher_new ((G_SUBPROCESS_FLAGS_STDERR_PIPE |
- G_SUBPROCESS_FLAGS_STDOUT_PIPE));
- g_subprocess_launcher_set_cwd (launcher, state->directory_path);
- g_subprocess_launcher_setenv (launcher, "LANG", "C", TRUE);
+ if (NULL == (launcher = ide_runtime_create_launcher (state->runtime, &error)))
+ {
+ g_task_return_error (task, error);
+ return FALSE;
+ }
+
+ ide_subprocess_launcher_set_flags (launcher,
+ (G_SUBPROCESS_FLAGS_STDERR_PIPE |
+ G_SUBPROCESS_FLAGS_STDOUT_PIPE));
+ ide_subprocess_launcher_set_cwd (launcher, state->directory_path);
+ ide_subprocess_launcher_setenv (launcher, "LANG", "C", TRUE);
+ apply_environment (self, launcher);
+
+ /*
+ * Try to locate GNU make within the runtime.
+ */
+ if (ide_runtime_contains_program_in_path (state->runtime, "gmake", cancellable))
+ make = "gmake";
+ else if (ide_runtime_contains_program_in_path (state->runtime, "make", cancellable))
+ make = "make";
+ else
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND,
+ "Failed to locate make.");
+ return FALSE;
+ }
if (!g_strv_length (state->make_targets))
targets = (const gchar * const *)default_targets;
@@ -932,7 +856,7 @@ step_make_all (GTask *task,
else
ide_build_result_set_mode (IDE_BUILD_RESULT (self), _("Building…"));
- process = log_and_spawn (self, launcher, &error, GNU_MAKE_NAME, target, state->parallel, NULL);
+ process = log_and_spawn (self, launcher, &error, make, target, state->parallel, NULL);
if (!process)
{
@@ -951,3 +875,16 @@ step_make_all (GTask *task,
return TRUE;
}
+
+static void
+apply_environment (IdeAutotoolsBuildTask *self,
+ IdeSubprocessLauncher *launcher)
+{
+ IdeEnvironment *environment;
+
+ g_assert (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
+ g_assert (IDE_IS_SUBPROCESS_LAUNCHER (launcher));
+
+ environment = ide_configuration_get_environment (self->configuration);
+ ide_subprocess_launcher_overlay_environment (launcher, environment);
+}
diff --git a/plugins/autotools/ide-autotools-build-task.h b/plugins/autotools/ide-autotools-build-task.h
index d9b801a..2dd19e1 100644
--- a/plugins/autotools/ide-autotools-build-task.h
+++ b/plugins/autotools/ide-autotools-build-task.h
@@ -27,16 +27,12 @@ G_BEGIN_DECLS
#define IDE_TYPE_AUTOTOOLS_BUILD_TASK (ide_autotools_build_task_get_type())
-G_DECLARE_FINAL_TYPE (IdeAutotoolsBuildTask, ide_autotools_build_task,
- IDE, AUTOTOOLS_BUILD_TASK, IdeBuildResult)
-
-struct _IdeAutotoolsBuildTask
-{
- IdeBuildResult parent_instance;
-};
+G_DECLARE_FINAL_TYPE (IdeAutotoolsBuildTask, ide_autotools_build_task, IDE, AUTOTOOLS_BUILD_TASK,
IdeBuildResult)
GFile *ide_autotools_build_task_get_directory (IdeAutotoolsBuildTask *self);
+void ide_autotools_build_task_add_target (IdeAutotoolsBuildTask *self);
void ide_autotools_build_task_execute_async (IdeAutotoolsBuildTask *self,
+ IdeBuilderBuildFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
diff --git a/plugins/autotools/ide-autotools-builder.c b/plugins/autotools/ide-autotools-builder.c
index 41c63b3..eda8e9d 100644
--- a/plugins/autotools/ide-autotools-builder.c
+++ b/plugins/autotools/ide-autotools-builder.c
@@ -17,99 +17,18 @@
*/
#include <glib/gi18n.h>
+#include <ide.h>
#include "ide-autotools-build-task.h"
#include "ide-autotools-builder.h"
-#include "ide-build-result.h"
-#include "ide-context.h"
-#include "ide-device.h"
-#include "ide-project.h"
-#include "ide-vcs.h"
struct _IdeAutotoolsBuilder
{
- IdeObject parent_instance;
-
- GKeyFile *config;
- IdeDevice *device;
+ IdeBuilder parent_instance;
};
G_DEFINE_TYPE (IdeAutotoolsBuilder, ide_autotools_builder, IDE_TYPE_BUILDER)
-enum {
- PROP_0,
- PROP_CONFIG,
- PROP_DEVICE,
- LAST_PROP
-};
-
-static GParamSpec *properties [LAST_PROP];
-
-static void
-ide_autotools_builder_merge_defaults (IdeAutotoolsBuilder *self,
- GKeyFile *key_file)
-{
- g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self));
- g_return_if_fail (key_file != NULL);
-
- if (!g_key_file_has_key (key_file, "parallel", "workers", NULL))
- {
- g_autoptr(GSettings) settings = g_settings_new ("org.gnome.builder.build");
-
- g_key_file_set_integer (key_file,
- "parallel", "workers",
- g_settings_get_int (settings, "parallel"));
- }
-}
-
-GKeyFile *
-ide_autotools_builder_get_config (IdeAutotoolsBuilder *self)
-{
- g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self), NULL);
-
- return self->config;
-}
-
-static void
-ide_autotools_builder_set_config (IdeAutotoolsBuilder *self,
- GKeyFile *config)
-{
- g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self));
-
- if (self->config != config)
- {
- g_clear_pointer (&self->config, g_key_file_unref);
-
- if (config != NULL)
- {
- self->config = g_key_file_ref (config);
- ide_autotools_builder_merge_defaults (self, config);
- }
-
- g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONFIG]);
- }
-}
-
-IdeDevice *
-ide_autotools_builder_get_device (IdeAutotoolsBuilder *self)
-{
- g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self), NULL);
-
- return self->device;
-}
-
-static void
-ide_autotools_builder_set_device (IdeAutotoolsBuilder *self,
- IdeDevice *device)
-{
- g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self));
- g_return_if_fail (!device || IDE_IS_DEVICE (device));
-
- if (self->device != device)
- if (g_set_object (&self->device, device))
- g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEVICE]);
-}
-
static void
ide_autotools_builder_build_cb (GObject *object,
GAsyncResult *result,
@@ -151,8 +70,10 @@ GFile *
ide_autotools_builder_get_build_directory (IdeAutotoolsBuilder *self)
{
g_autofree gchar *path = NULL;
+ IdeConfiguration *configuration;
IdeContext *context;
IdeProject *project;
+ IdeDevice *device;
const gchar *root_build_dir;
const gchar *project_name;
const gchar *device_id;
@@ -161,7 +82,11 @@ ide_autotools_builder_get_build_directory (IdeAutotoolsBuilder *self)
g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self), NULL);
context = ide_object_get_context (IDE_OBJECT (self));
- device_id = ide_device_get_id (self->device);
+
+ configuration = ide_builder_get_configuration (IDE_BUILDER (self));
+
+ device = ide_configuration_get_device (configuration);
+ device_id = ide_device_get_id (device);
/*
* If this is the local device, we have a special workaround for building within the project
@@ -196,7 +121,7 @@ ide_autotools_builder_get_build_directory (IdeAutotoolsBuilder *self)
project = ide_context_get_project (context);
root_build_dir = ide_context_get_root_build_dir (context);
- system_type = ide_device_get_system_type (self->device);
+ system_type = ide_device_get_system_type (device);
project_name = ide_project_get_name (project);
path = g_build_filename (root_build_dir, project_name, device_id, system_type, NULL);
@@ -215,29 +140,23 @@ ide_autotools_builder_build_async (IdeBuilder *builder,
g_autoptr(IdeAutotoolsBuildTask) build_result = NULL;
g_autoptr(GTask) task = NULL;
g_autoptr(GFile) directory = NULL;
+ IdeConfiguration *configuration;
IdeContext *context;
- IdeDevice *device;
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (builder));
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self));
- if (flags & IDE_BUILDER_BUILD_FLAGS_FORCE_REBUILD)
- g_key_file_set_boolean (self->config, "autotools", "rebuild", TRUE);
-
- /* TODO: This belongs as its own vfunc */
- if (flags & IDE_BUILDER_BUILD_FLAGS_CLEAN)
- g_key_file_set_boolean (self->config, "autotools", "clean-only", TRUE);
+ if (ide_autotools_builder_get_needs_bootstrap (self))
+ flags |= IDE_BUILDER_BUILD_FLAGS_FORCE_BOOTSTRAP;
task = g_task_new (self, cancellable, callback, user_data);
context = ide_object_get_context (IDE_OBJECT (builder));
- device = ide_autotools_builder_get_device (self);
+ configuration = ide_builder_get_configuration (IDE_BUILDER (self));
directory = ide_autotools_builder_get_build_directory (self);
-
build_result = g_object_new (IDE_TYPE_AUTOTOOLS_BUILD_TASK,
"context", context,
- "config", self->config,
- "device", device,
+ "configuration", configuration,
"directory", directory,
"mode", _("Building…"),
"running", TRUE,
@@ -247,6 +166,7 @@ ide_autotools_builder_build_async (IdeBuilder *builder,
*result = g_object_ref (build_result);
ide_autotools_build_task_execute_async (build_result,
+ flags,
cancellable,
ide_autotools_builder_build_cb,
g_object_ref (task));
@@ -266,94 +186,12 @@ ide_autotools_builder_build_finish (IdeBuilder *builder,
}
static void
-ide_autotools_builder_finalize (GObject *object)
-{
- IdeAutotoolsBuilder *self = (IdeAutotoolsBuilder *)object;
-
- g_clear_pointer (&self->config, g_key_file_unref);
- g_clear_object (&self->device);
-
- G_OBJECT_CLASS (ide_autotools_builder_parent_class)->finalize (object);
-}
-
-static void
-ide_autotools_builder_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- IdeAutotoolsBuilder *self = IDE_AUTOTOOLS_BUILDER (object);
-
- switch (prop_id)
- {
- case PROP_CONFIG:
- g_value_set_boxed (value, ide_autotools_builder_get_config (self));
- break;
-
- case PROP_DEVICE:
- g_value_set_object (value, ide_autotools_builder_get_device (self));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- }
-}
-
-static void
-ide_autotools_builder_set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- IdeAutotoolsBuilder *self = IDE_AUTOTOOLS_BUILDER (object);
-
- switch (prop_id)
- {
- case PROP_CONFIG:
- ide_autotools_builder_set_config (self, g_value_get_boxed (value));
- break;
-
- case PROP_DEVICE:
- ide_autotools_builder_set_device (self, g_value_get_object (value));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- }
-}
-
-static void
ide_autotools_builder_class_init (IdeAutotoolsBuilderClass *klass)
{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
IdeBuilderClass *builder_class = IDE_BUILDER_CLASS (klass);
- object_class->finalize = ide_autotools_builder_finalize;
- object_class->get_property = ide_autotools_builder_get_property;
- object_class->set_property = ide_autotools_builder_set_property;
-
builder_class->build_async = ide_autotools_builder_build_async;
builder_class->build_finish = ide_autotools_builder_build_finish;
-
- properties [PROP_CONFIG] =
- g_param_spec_boxed ("config",
- "Config",
- "The configuration for the build.",
- G_TYPE_KEY_FILE,
- (G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_STATIC_STRINGS));
-
- properties [PROP_DEVICE] =
- g_param_spec_object ("device",
- "Device",
- "The device to build for.",
- IDE_TYPE_DEVICE,
- (G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_STATIC_STRINGS));
-
- g_object_class_install_properties (object_class, LAST_PROP, properties);
}
static void
@@ -366,6 +204,7 @@ ide_autotools_builder_get_needs_bootstrap (IdeAutotoolsBuilder *self)
{
g_autoptr(GFile) configure = NULL;
GFile *working_directory = NULL;
+ IdeConfiguration *configuration;
IdeContext *context;
IdeVcs *vcs;
@@ -379,6 +218,10 @@ ide_autotools_builder_get_needs_bootstrap (IdeAutotoolsBuilder *self)
if (!g_file_query_exists (configure, NULL))
return TRUE;
+ configuration = ide_builder_get_configuration (IDE_BUILDER (self));
+ if (ide_configuration_get_dirty (configuration))
+ return TRUE;
+
/*
* TODO:
*
@@ -389,33 +232,3 @@ ide_autotools_builder_get_needs_bootstrap (IdeAutotoolsBuilder *self)
return FALSE;
}
-
-void
-ide_autotools_builder_bootstrap_async (IdeAutotoolsBuilder *self,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- g_autoptr(GTask) task = NULL;
-
- g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self));
- g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
-
- task = g_task_new (self, cancellable, callback, user_data);
-
- g_key_file_set_boolean (self->config, "autotools", "bootstrap", TRUE);
-
- g_task_return_boolean (task, TRUE);
-}
-
-gboolean
-ide_autotools_builder_bootstrap_finish (IdeAutotoolsBuilder *self,
- GAsyncResult *result,
- GError **error)
-{
- GTask *task = (GTask *)result;
-
- g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self), FALSE);
-
- return g_task_propagate_boolean (task, error);
-}
diff --git a/plugins/autotools/ide-autotools-builder.h b/plugins/autotools/ide-autotools-builder.h
index 04f8821..d6558cf 100644
--- a/plugins/autotools/ide-autotools-builder.h
+++ b/plugins/autotools/ide-autotools-builder.h
@@ -29,13 +29,6 @@ G_DECLARE_FINAL_TYPE (IdeAutotoolsBuilder, ide_autotools_builder, IDE, AUTOTOOLS
GFile *ide_autotools_builder_get_build_directory (IdeAutotoolsBuilder *self);
gboolean ide_autotools_builder_get_needs_bootstrap (IdeAutotoolsBuilder *self);
-void ide_autotools_builder_bootstrap_async (IdeAutotoolsBuilder *self,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean ide_autotools_builder_bootstrap_finish (IdeAutotoolsBuilder *self,
- GAsyncResult *result,
- GError **error);
G_END_DECLS
diff --git a/plugins/build-tools/Makefile.am b/plugins/build-tools/Makefile.am
index 7c7da83..a0c9fb7 100644
--- a/plugins/build-tools/Makefile.am
+++ b/plugins/build-tools/Makefile.am
@@ -10,12 +10,18 @@ plugin_LTLIBRARIES = libbuild-tools-plugin.la
dist_plugin_DATA = build-tools.plugin
libbuild_tools_plugin_la_SOURCES = \
+ gbp-build-configuration-row.c \
+ gbp-build-configuration-row.h \
+ gbp-build-configuration-view.c \
+ gbp-build-configuration-view.h \
gbp-build-log-panel.c \
gbp-build-log-panel.h \
gbp-build-panel.c \
gbp-build-panel.h \
gbp-build-panel-row.c \
gbp-build-panel-row.h \
+ gbp-build-perspective.c \
+ gbp-build-perspective.h \
gbp-build-plugin.c \
gbp-build-tool.c \
gbp-build-tool.h \
diff --git a/plugins/build-tools/gbp-build-configuration-row.c
b/plugins/build-tools/gbp-build-configuration-row.c
new file mode 100644
index 0000000..d77a860
--- /dev/null
+++ b/plugins/build-tools/gbp-build-configuration-row.c
@@ -0,0 +1,153 @@
+/* gbp-build-configuration-row.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gbp-build-configuration-row.h"
+
+struct _GbpBuildConfigurationRow
+{
+ GtkListBoxRow parent_instance;
+
+ IdeConfiguration *configuration;
+
+ GtkLabel *label;
+ GtkImage *check_image;
+};
+
+enum {
+ PROP_0,
+ PROP_CONFIGURATION,
+ PROP_SELECTED,
+ LAST_PROP
+};
+
+G_DEFINE_TYPE (GbpBuildConfigurationRow, gbp_build_configuration_row, GTK_TYPE_LIST_BOX_ROW)
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+gbp_build_configuration_row_set_configuration (GbpBuildConfigurationRow *self,
+ IdeConfiguration *configuration)
+{
+ g_assert (GBP_IS_BUILD_CONFIGURATION_ROW (self));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ g_set_object (&self->configuration, configuration);
+
+ g_object_bind_property (configuration, "display-name", self->label, "label", G_BINDING_SYNC_CREATE);
+}
+
+static void
+gbp_build_configuration_row_finalize (GObject *object)
+{
+ GbpBuildConfigurationRow *self = (GbpBuildConfigurationRow *)object;
+
+ g_clear_object (&self->configuration);
+
+ G_OBJECT_CLASS (gbp_build_configuration_row_parent_class)->finalize (object);
+}
+
+static void
+gbp_build_configuration_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbpBuildConfigurationRow *self = GBP_BUILD_CONFIGURATION_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIGURATION:
+ g_value_set_object (value, gbp_build_configuration_row_get_configuration (self));
+ break;
+
+ case PROP_SELECTED:
+ g_value_set_boolean (value, gtk_widget_get_visible (GTK_WIDGET (self->check_image)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gbp_build_configuration_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbpBuildConfigurationRow *self = GBP_BUILD_CONFIGURATION_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIGURATION:
+ gbp_build_configuration_row_set_configuration (self, g_value_get_object (value));
+ break;
+
+ case PROP_SELECTED:
+ gtk_widget_set_visible (GTK_WIDGET (self->check_image), g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gbp_build_configuration_row_class_init (GbpBuildConfigurationRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gbp_build_configuration_row_finalize;
+ object_class->get_property = gbp_build_configuration_row_get_property;
+ object_class->set_property = gbp_build_configuration_row_set_property;
+
+ properties [PROP_CONFIGURATION] =
+ g_param_spec_object ("configuration",
+ "Configuration",
+ "The configuration this row represents",
+ IDE_TYPE_CONFIGURATION,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SELECTED] =
+ g_param_spec_boolean ("selected",
+ "Selected",
+ "If the row is selected",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/builder/plugins/build-tools-plugin/gbp-build-configuration-row.ui");
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildConfigurationRow, check_image);
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildConfigurationRow, label);
+}
+
+static void
+gbp_build_configuration_row_init (GbpBuildConfigurationRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+IdeConfiguration *
+gbp_build_configuration_row_get_configuration (GbpBuildConfigurationRow *self)
+{
+ g_return_val_if_fail (GBP_IS_BUILD_CONFIGURATION_ROW (self), NULL);
+
+ return self->configuration;
+}
diff --git a/plugins/build-tools/gbp-build-configuration-row.h
b/plugins/build-tools/gbp-build-configuration-row.h
new file mode 100644
index 0000000..6bf4d65
--- /dev/null
+++ b/plugins/build-tools/gbp-build-configuration-row.h
@@ -0,0 +1,36 @@
+/* gbp-build-configuration-row.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GBP_BUILD_CONFIGURATION_ROW_H
+#define GBP_BUILD_CONFIGURATION_ROW_H
+
+#include <gtk/gtk.h>
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_BUILD_CONFIGURATION_ROW (gbp_build_configuration_row_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpBuildConfigurationRow, gbp_build_configuration_row, GBP, BUILD_CONFIGURATION_ROW,
GtkListBoxRow)
+
+GtkWidget *gbp_build_configuration_row_new (IdeConfiguration *configuration);
+IdeConfiguration *gbp_build_configuration_row_get_configuration (GbpBuildConfigurationRow *self);
+
+G_END_DECLS
+
+#endif /* GBP_BUILD_CONFIGURATION_ROW_H */
diff --git a/plugins/build-tools/gbp-build-configuration-row.ui
b/plugins/build-tools/gbp-build-configuration-row.ui
new file mode 100644
index 0000000..10157e1
--- /dev/null
+++ b/plugins/build-tools/gbp-build-configuration-row.ui
@@ -0,0 +1,33 @@
+<interface>
+ <template class="GbpBuildConfigurationRow" parent="GtkListBoxRow">
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="valign">baseline</property>
+ <property name="visible">true</property>
+ <property name="xalign">0.0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="check_image">
+ <property name="icon-name">object-select-symbolic</property>
+ <property name="margin-end">6</property>
+ <property name="margin-start">6</property>
+ <property name="valign">baseline</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="spacer">
+ <property name="hexpand">true</property>
+ <property name="valign">baseline</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/plugins/build-tools/gbp-build-configuration-view.c
b/plugins/build-tools/gbp-build-configuration-view.c
new file mode 100644
index 0000000..fd76b74
--- /dev/null
+++ b/plugins/build-tools/gbp-build-configuration-view.c
@@ -0,0 +1,417 @@
+/* gbp-build-configuration-view.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ide.h>
+#include <string.h>
+
+#include "gbp-build-configuration-view.h"
+
+#include "ide-internal.h"
+
+struct _GbpBuildConfigurationView
+{
+ EggColumnLayout parent_instance;
+
+ IdeConfiguration *configuration;
+
+ GBinding *configure_binding;
+ GBinding *display_name_binding;
+ GBinding *prefix_binding;
+
+ GtkEntry *configure_entry;
+ GtkListBox *device_list_box;
+ GtkEntry *display_name_entry;
+ IdeEnvironmentEditor *environment_editor;
+ GtkEntry *prefix_entry;
+ GtkListBox *runtime_list_box;
+};
+
+enum {
+ PROP_0,
+ PROP_CONFIGURATION,
+ LAST_PROP
+};
+
+G_DEFINE_TYPE (GbpBuildConfigurationView, gbp_build_configuration_view, EGG_TYPE_COLUMN_LAYOUT)
+
+static GParamSpec *properties [LAST_PROP];
+
+static gboolean
+map_pointer_to (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ g_value_set_boolean (to_value, (user_data == g_value_get_object (from_value)));
+ return TRUE;
+}
+
+static GtkWidget *
+create_runtime_row (gpointer item,
+ gpointer user_data)
+{
+ IdeRuntime *runtime = item;
+ IdeConfiguration *configuration = user_data;
+ GtkWidget *box;
+ GtkWidget *image;
+ GtkWidget *label;
+ GtkWidget *row;
+
+ g_assert (IDE_IS_RUNTIME (runtime));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ box = g_object_new (GTK_TYPE_BOX,
+ "spacing", 12,
+ "visible", TRUE,
+ NULL);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "use-markup", TRUE,
+ "visible", TRUE,
+ "xalign", 0.0f,
+ NULL);
+ g_object_bind_property (runtime, "display-name", label, "label", G_BINDING_SYNC_CREATE);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ image = g_object_new (GTK_TYPE_IMAGE,
+ "icon-name", "object-select-symbolic",
+ "visible", TRUE,
+ NULL);
+ g_object_bind_property_full (configuration, "runtime",
+ image, "visible",
+ G_BINDING_SYNC_CREATE,
+ map_pointer_to,
+ NULL,
+ g_object_ref (runtime),
+ g_object_unref);
+ gtk_container_add (GTK_CONTAINER (box), image);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "hexpand", TRUE,
+ "visible", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+ "child", box,
+ "visible", TRUE,
+ NULL);
+
+ g_object_set_data (G_OBJECT (row), "IDE_RUNTIME", runtime);
+
+ return row;
+}
+
+static GtkWidget *
+create_device_row (gpointer item,
+ gpointer user_data)
+{
+ IdeDevice *device = item;
+ IdeConfiguration *configuration = user_data;
+ GtkWidget *box;
+ GtkWidget *image;
+ GtkWidget *label;
+ GtkWidget *row;
+
+ g_assert (IDE_IS_DEVICE (device));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ box = g_object_new (GTK_TYPE_BOX,
+ "spacing", 12,
+ "visible", TRUE,
+ NULL);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "use-markup", TRUE,
+ "visible", TRUE,
+ "xalign", 0.0f,
+ NULL);
+ g_object_bind_property (device, "display-name", label, "label", G_BINDING_SYNC_CREATE);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ image = g_object_new (GTK_TYPE_IMAGE,
+ "icon-name", "object-select-symbolic",
+ "visible", TRUE,
+ NULL);
+ g_object_bind_property_full (configuration, "device",
+ image, "visible",
+ G_BINDING_SYNC_CREATE,
+ map_pointer_to,
+ NULL,
+ g_object_ref (device),
+ g_object_unref);
+ gtk_container_add (GTK_CONTAINER (box), image);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "hexpand", TRUE,
+ "visible", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+ "child", box,
+ "visible", TRUE,
+ NULL);
+
+ g_object_set_data (G_OBJECT (row), "IDE_DEVICE", device);
+
+ return row;
+}
+
+static void
+device_row_activated (GbpBuildConfigurationView *self,
+ GtkListBoxRow *row,
+ GtkListBox *list_box)
+{
+ IdeDevice *device;
+
+ g_assert (GBP_IS_BUILD_CONFIGURATION_VIEW (self));
+ g_assert (GTK_IS_LIST_BOX_ROW (row));
+ g_assert (GTK_IS_LIST_BOX (list_box));
+
+ device = g_object_get_data (G_OBJECT (row), "IDE_DEVICE");
+
+ if (self->configuration != NULL)
+ ide_configuration_set_device (self->configuration, device);
+}
+
+static void
+runtime_row_activated (GbpBuildConfigurationView *self,
+ GtkListBoxRow *row,
+ GtkListBox *list_box)
+{
+ IdeRuntime *runtime;
+
+ g_assert (GBP_IS_BUILD_CONFIGURATION_VIEW (self));
+ g_assert (GTK_IS_LIST_BOX_ROW (row));
+ g_assert (GTK_IS_LIST_BOX (list_box));
+
+ runtime = g_object_get_data (G_OBJECT (row), "IDE_RUNTIME");
+
+ if (self->configuration != NULL)
+ ide_configuration_set_runtime (self->configuration, runtime);
+}
+
+static gboolean
+treat_null_as_empty (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ const gchar *str = g_value_get_string (from_value);
+ g_value_set_string (to_value, str ?: "");
+ return TRUE;
+}
+
+static void
+gbp_build_configuration_view_connect (GbpBuildConfigurationView *self,
+ IdeConfiguration *configuration)
+{
+ IdeRuntimeManager *runtime_manager;
+ IdeDeviceManager *device_manager;
+ IdeContext *context;
+ IdeEnvironment *environment;
+
+ g_assert (GBP_IS_BUILD_CONFIGURATION_VIEW (self));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ context = ide_object_get_context (IDE_OBJECT (configuration));
+ runtime_manager = ide_context_get_runtime_manager (context);
+ device_manager = ide_context_get_device_manager (context);
+
+ self->display_name_binding =
+ g_object_bind_property_full (configuration, "display-name",
+ self->display_name_entry, "text",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+ treat_null_as_empty, NULL, NULL, NULL);
+
+ self->configure_binding =
+ g_object_bind_property_full (configuration, "config-opts",
+ self->configure_entry, "text",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+ treat_null_as_empty, NULL, NULL, NULL);
+
+ self->prefix_binding =
+ g_object_bind_property_full (configuration, "prefix",
+ self->prefix_entry, "text",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+ treat_null_as_empty, NULL, NULL, NULL);
+
+ gtk_list_box_bind_model (self->runtime_list_box,
+ G_LIST_MODEL (runtime_manager),
+ create_runtime_row,
+ g_object_ref (configuration),
+ g_object_unref);
+
+ gtk_list_box_bind_model (self->device_list_box,
+ G_LIST_MODEL (device_manager),
+ create_device_row,
+ g_object_ref (configuration),
+ g_object_unref);
+
+ environment = ide_configuration_get_environment (configuration);
+ ide_environment_editor_set_environment (self->environment_editor, environment);
+}
+
+static void
+gbp_build_configuration_view_disconnect (GbpBuildConfigurationView *self,
+ IdeConfiguration *configuration)
+{
+ g_assert (GBP_IS_BUILD_CONFIGURATION_VIEW (self));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ gtk_list_box_bind_model (self->device_list_box, NULL, NULL, NULL, NULL);
+ gtk_list_box_bind_model (self->runtime_list_box, NULL, NULL, NULL, NULL);
+
+ g_clear_pointer (&self->configure_binding, g_binding_unbind);
+ g_clear_pointer (&self->display_name_binding, g_binding_unbind);
+ g_clear_pointer (&self->prefix_binding, g_binding_unbind);
+}
+
+static void
+gbp_build_configuration_view_destroy (GtkWidget *widget)
+{
+ GbpBuildConfigurationView *self = (GbpBuildConfigurationView *)widget;
+
+ if (self->configuration != NULL)
+ {
+ gbp_build_configuration_view_disconnect (self, self->configuration);
+ g_clear_object (&self->configuration);
+ }
+
+ GTK_WIDGET_CLASS (gbp_build_configuration_view_parent_class)->destroy (widget);
+}
+
+static void
+gbp_build_configuration_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbpBuildConfigurationView *self = GBP_BUILD_CONFIGURATION_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIGURATION:
+ g_value_set_object (value, gbp_build_configuration_view_get_configuration (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gbp_build_configuration_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbpBuildConfigurationView *self = GBP_BUILD_CONFIGURATION_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIGURATION:
+ gbp_build_configuration_view_set_configuration (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gbp_build_configuration_view_class_init (GbpBuildConfigurationViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = gbp_build_configuration_view_get_property;
+ object_class->set_property = gbp_build_configuration_view_set_property;
+
+ properties [PROP_CONFIGURATION] =
+ g_param_spec_object ("configuration",
+ "Configuration",
+ "Configuration",
+ IDE_TYPE_CONFIGURATION,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ widget_class->destroy = gbp_build_configuration_view_destroy;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/builder/plugins/build-tools-plugin/gbp-build-configuration-view.ui");
+ gtk_widget_class_set_css_name (widget_class, "configurationview");
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildConfigurationView, configure_entry);
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildConfigurationView, device_list_box);
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildConfigurationView, display_name_entry);
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildConfigurationView, environment_editor);
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildConfigurationView, prefix_entry);
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildConfigurationView, runtime_list_box);
+}
+
+static void
+gbp_build_configuration_view_init (GbpBuildConfigurationView *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect_object (self->device_list_box,
+ "row-activated",
+ G_CALLBACK (device_row_activated),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->runtime_list_box,
+ "row-activated",
+ G_CALLBACK (runtime_row_activated),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+IdeConfiguration *
+gbp_build_configuration_view_get_configuration (GbpBuildConfigurationView *self)
+{
+ g_return_val_if_fail (GBP_IS_BUILD_CONFIGURATION_VIEW (self), NULL);
+
+ return self->configuration;
+}
+
+void
+gbp_build_configuration_view_set_configuration (GbpBuildConfigurationView *self,
+ IdeConfiguration *configuration)
+{
+ g_return_if_fail (GBP_IS_BUILD_CONFIGURATION_VIEW (self));
+ g_return_if_fail (!configuration || IDE_IS_CONFIGURATION (configuration));
+
+ if (self->configuration != configuration)
+ {
+ if (self->configuration != NULL)
+ {
+ gbp_build_configuration_view_disconnect (self, self->configuration);
+ g_clear_object (&self->configuration);
+ }
+
+ if (configuration != NULL)
+ {
+ self->configuration = g_object_ref (configuration);
+ gbp_build_configuration_view_connect (self, configuration);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONFIGURATION]);
+ }
+}
diff --git a/plugins/build-tools/gbp-build-configuration-view.h
b/plugins/build-tools/gbp-build-configuration-view.h
new file mode 100644
index 0000000..37a00a1
--- /dev/null
+++ b/plugins/build-tools/gbp-build-configuration-view.h
@@ -0,0 +1,38 @@
+/* gbp-build-configuration-view.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GBP_BUILD_CONFIGURATION_VIEW_H
+#define GBP_BUILD_CONFIGURATION_VIEW_H
+
+#include <ide.h>
+
+#include "egg-column-layout.h"
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_BUILD_CONFIGURATION_VIEW (gbp_build_configuration_view_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpBuildConfigurationView, gbp_build_configuration_view, GBP,
BUILD_CONFIGURATION_VIEW, EggColumnLayout)
+
+IdeConfiguration *gbp_build_configuration_view_get_configuration (GbpBuildConfigurationView *self);
+void gbp_build_configuration_view_set_configuration (GbpBuildConfigurationView *self,
+ IdeConfiguration *configuration);
+
+G_END_DECLS
+
+#endif /* GBP_BUILD_CONFIGURATION_VIEW_H */
diff --git a/plugins/build-tools/gbp-build-configuration-view.ui
b/plugins/build-tools/gbp-build-configuration-view.ui
new file mode 100644
index 0000000..cb978f6
--- /dev/null
+++ b/plugins/build-tools/gbp-build-configuration-view.ui
@@ -0,0 +1,268 @@
+<interface>
+ <template class="GbpBuildConfigurationView" parent="EggColumnLayout">
+ <property name="border-width">24</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">General</property>
+ <property name="xalign">0.0</property>
+ <property name="visible">true</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkListBox">
+ <property name="selection-mode">none</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="tooltip-text" translatable="yes">The name of the build
configuration</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <property name="orientation">horizontal</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="label" translatable="yes">Name</property>
+ <property name="xalign">0.0</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="display_name_entry">
+ <property name="has-frame">false</property>
+ <property name="hexpand">true</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="tooltip-text" translatable="yes">The prefix to use when installiing the
project</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <property name="orientation">horizontal</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="label" translatable="yes">Installation Prefix</property>
+ <property name="visible">true</property>
+ <property name="xalign">0.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="prefix_entry">
+ <property name="visible">true</property>
+ <property name="has-frame">false</property>
+ <property name="hexpand">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="tooltip-text" translatable="yes">Options to use when bootstrapping the
project</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <property name="orientation">horizontal</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="label" translatable="yes">Configure Options</property>
+ <property name="visible">true</property>
+ <property name="xalign">0.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="configure_entry">
+ <property name="visible">true</property>
+ <property name="has-frame">false</property>
+ <property name="hexpand">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkListBox">
+ <property name="selection-mode">none</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkListBoxRow">
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <property name="orientation">horizontal</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkButton">
+ <property name="action-name">perspective.duplicate-configuration</property>
+ <property name="label" translatable="yes">Duplicate Configuration</property>
+ <property name="valign">center</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="hexpand">true</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="action-name">perspective.delete-configuration</property>
+ <property name="label" translatable="yes">Delete Configuration</property>
+ <property name="valign">center</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Device</property>
+ <property name="xalign">0.0</property>
+ <property name="visible">true</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkListBox" id="device_list_box">
+ <property name="selection-mode">none</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Runtime</property>
+ <property name="xalign">0.0</property>
+ <property name="visible">true</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkListBox" id="runtime_list_box">
+ <property name="selection-mode">none</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Environment</property>
+ <property name="xalign">0.0</property>
+ <property name="visible">true</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">true</property>
+ <child>
+ <object class="IdeEnvironmentEditor" id="environment_editor">
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkSizeGroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="label1"/>
+ <widget name="label2"/>
+ <widget name="label3"/>
+ </widgets>
+ </object>
+ <object class="GtkListStore" id="environ_list_store">
+ <columns>
+ <column type="gchararray"/>
+ <column type="gchararray"/>
+ </columns>
+ </object>
+</interface>
diff --git a/plugins/build-tools/gbp-build-panel.c b/plugins/build-tools/gbp-build-panel.c
index 4a84da0..1b703bd 100644
--- a/plugins/build-tools/gbp-build-panel.c
+++ b/plugins/build-tools/gbp-build-panel.c
@@ -22,6 +22,7 @@
#include "egg-binding-group.h"
#include "egg-signal-group.h"
+#include "gbp-build-configuration-row.h"
#include "gbp-build-panel.h"
#include "gbp-build-panel-row.h"
@@ -33,17 +34,14 @@ struct _GbpBuildPanel
EggSignalGroup *signals;
EggBindingGroup *bindings;
- IdeDevice *device;
-
+ GtkListBox *configurations;
+ GtkLabel *configuration_label;
+ GtkPopover *configuration_popover;
GtkListBox *diagnostics;
+ GtkLabel *errors_label;
+ GtkLabel *running_time_label;
GtkRevealer *status_revealer;
GtkLabel *status_label;
- GtkLabel *running_time_label;
- GtkMenuButton *device_button;
- GtkLabel *device_label;
- GtkListBox *devices;
- GtkPopover *device_popover;
- GtkLabel *errors_label;
GtkLabel *warnings_label;
guint running_time_source;
@@ -56,79 +54,70 @@ G_DEFINE_TYPE (GbpBuildPanel, gbp_build_panel, GTK_TYPE_BIN)
enum {
PROP_0,
- PROP_DEVICE,
- PROP_DEVICE_MANAGER,
+ PROP_CONFIGURATION_MANAGER,
PROP_RESULT,
LAST_PROP
};
static GParamSpec *properties [LAST_PROP];
-static GtkWidget *
-create_device_row (gpointer item,
- gpointer user_data)
+static gboolean
+map_current_to_bool (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
{
- GtkListBoxRow *row;
- IdeDevice *device = item;
- const gchar *type;
- const gchar *name;
- GtkLabel *label;
- gchar *str;
-
- g_assert (IDE_IS_DEVICE (device));
+ IdeConfiguration *configuration = user_data;
+ IdeConfiguration *current;
- row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
- "visible", TRUE,
- NULL);
-
- g_object_set_data_full (G_OBJECT (row),
- "IDE_DEVICE_ID",
- g_strdup (ide_device_get_id (device)),
- g_free);
-
- name = ide_device_get_display_name (device);
- type = ide_device_get_system_type (device);
- str = g_strdup_printf ("%s (%s)", name, type);
-
- label = g_object_new (GTK_TYPE_LABEL,
- "label", str,
- "xalign", 0.0f,
- "visible", TRUE,
- NULL);
- gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (label));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
- g_free (str);
+ current = g_value_get_object (from_value);
+ g_value_set_boolean (to_value, (configuration == current));
- return GTK_WIDGET (row);
+ return TRUE;
}
-static void
-gbp_build_panel_set_device (GbpBuildPanel *self,
- IdeDevice *device)
+static GtkWidget *
+create_configuration_row (gpointer item,
+ gpointer user_data)
{
- g_return_if_fail (GBP_IS_BUILD_PANEL (self));
- g_return_if_fail (!device || IDE_IS_DEVICE (device));
+ IdeConfiguration *configuration = item;
+ IdeConfigurationManager *manager = user_data;
+ GtkWidget *ret;
- if (g_set_object (&self->device, device))
- {
- const gchar *name = NULL;
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
- if (device != NULL)
- name = ide_device_get_display_name (device);
- gtk_label_set_label (self->device_label, name);
- }
+ ret = g_object_new (GBP_TYPE_BUILD_CONFIGURATION_ROW,
+ "configuration", configuration,
+ "visible", TRUE,
+ NULL);
+
+ g_object_bind_property_full (manager, "current", ret, "selected",
+ G_BINDING_SYNC_CREATE,
+ map_current_to_bool, NULL,
+ g_object_ref (configuration), g_object_unref);
+
+ return ret;
}
static void
-gbp_build_panel_set_device_manager (GbpBuildPanel *self,
- IdeDeviceManager *device_manager)
+gbp_build_panel_set_configuration_manager (GbpBuildPanel *self,
+ IdeConfigurationManager *configuration_manager)
{
- g_return_if_fail (GBP_IS_BUILD_PANEL (self));
- g_return_if_fail (!device_manager || IDE_IS_DEVICE_MANAGER (device_manager));
+ g_assert (GBP_IS_BUILD_PANEL (self));
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (configuration_manager));
- gtk_list_box_bind_model (self->devices,
- G_LIST_MODEL (device_manager),
- create_device_row, NULL, NULL);
+ gtk_list_box_bind_model (self->configurations,
+ G_LIST_MODEL (configuration_manager),
+ create_configuration_row,
+ g_object_ref (configuration_manager),
+ g_object_unref);
+
+ g_object_bind_property (configuration_manager, "current-display-name",
+ self->configuration_label, "label",
+ G_BINDING_SYNC_CREATE);
}
void
@@ -281,22 +270,26 @@ gbp_build_panel_notify_running (GbpBuildPanel *self,
}
static void
-gbp_build_panel_device_activated (GbpBuildPanel *self,
- GtkListBoxRow *row,
- GtkListBox *list_box)
+gbp_build_panel_configuration_activated (GbpBuildPanel *self,
+ GtkListBoxRow *row,
+ GtkListBox *list_box)
{
- const gchar *id;
+ IdeConfigurationManager *manager;
+ IdeConfiguration *config;
+ IdeWorkbench *workbench;
+ IdeContext *context;
g_assert (GBP_IS_BUILD_PANEL (self));
g_assert (GTK_IS_LIST_BOX_ROW (row));
g_assert (GTK_IS_LIST_BOX (list_box));
- if ((id = g_object_get_data (G_OBJECT (row), "IDE_DEVICE_ID")))
- ide_widget_action (GTK_WIDGET (self),
- "build-tools", "device",
- g_variant_new_string (id));
+ workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+ context = ide_workbench_get_context (workbench);
+ manager = ide_context_get_configuration_manager (context);
+ config = gbp_build_configuration_row_get_configuration (GBP_BUILD_CONFIGURATION_ROW (row));
+ ide_configuration_manager_set_current (manager, config);
- gtk_widget_hide (GTK_WIDGET (self->device_popover));
+ gtk_widget_hide (GTK_WIDGET (self->configuration_popover));
}
static void
@@ -339,7 +332,6 @@ gbp_build_panel_destroy (GtkWidget *widget)
g_clear_object (&self->bindings);
g_clear_object (&self->signals);
- g_clear_object (&self->device);
GTK_WIDGET_CLASS (gbp_build_panel_parent_class)->destroy (widget);
}
@@ -354,10 +346,6 @@ gbp_build_panel_get_property (GObject *object,
switch (prop_id)
{
- case PROP_DEVICE:
- g_value_set_object (value, self->device);
- break;
-
case PROP_RESULT:
g_value_set_object (value, self->result);
break;
@@ -377,12 +365,8 @@ gbp_build_panel_set_property (GObject *object,
switch (prop_id)
{
- case PROP_DEVICE:
- gbp_build_panel_set_device (self, g_value_get_object (value));
- break;
-
- case PROP_DEVICE_MANAGER:
- gbp_build_panel_set_device_manager (self, g_value_get_object (value));
+ case PROP_CONFIGURATION_MANAGER:
+ gbp_build_panel_set_configuration_manager (self, g_value_get_object (value));
break;
case PROP_RESULT:
@@ -405,18 +389,11 @@ gbp_build_panel_class_init (GbpBuildPanelClass *klass)
widget_class->destroy = gbp_build_panel_destroy;
- properties [PROP_DEVICE] =
- g_param_spec_object ("device",
- "Device",
- "Device",
- IDE_TYPE_DEVICE,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
- properties [PROP_DEVICE_MANAGER] =
- g_param_spec_object ("device-manager",
- "Device Manager",
- "Device Manager",
- IDE_TYPE_DEVICE_MANAGER,
+ properties [PROP_CONFIGURATION_MANAGER] =
+ g_param_spec_object ("configuration-manager",
+ "Configuration Manager",
+ "Configuration Manager",
+ IDE_TYPE_CONFIGURATION_MANAGER,
(G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
properties [PROP_RESULT] =
@@ -430,10 +407,9 @@ gbp_build_panel_class_init (GbpBuildPanelClass *klass)
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/builder/plugins/build-tools-plugin/gbp-build-panel.ui");
gtk_widget_class_set_css_name (widget_class, "buildpanel");
- gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, device_button);
- gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, device_label);
- gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, device_popover);
- gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, devices);
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, configurations);
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, configuration_label);
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, configuration_popover);
gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, diagnostics);
gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, errors_label);
gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, running_time_label);
@@ -461,9 +437,9 @@ gbp_build_panel_init (GbpBuildPanel *self)
self,
G_CONNECT_SWAPPED);
- g_signal_connect_object (self->devices,
+ g_signal_connect_object (self->configurations,
"row-activated",
- G_CALLBACK (gbp_build_panel_device_activated),
+ G_CALLBACK (gbp_build_panel_configuration_activated),
self,
G_CONNECT_SWAPPED);
@@ -475,7 +451,6 @@ gbp_build_panel_init (GbpBuildPanel *self)
self->bindings = egg_binding_group_new ();
- egg_binding_group_bind (self->bindings, "mode",
- self->status_label, "label",
+ egg_binding_group_bind (self->bindings, "mode", self->status_label, "label",
G_BINDING_SYNC_CREATE);
}
diff --git a/plugins/build-tools/gbp-build-panel.ui b/plugins/build-tools/gbp-build-panel.ui
index 8cbc219..9b29a5c 100644
--- a/plugins/build-tools/gbp-build-panel.ui
+++ b/plugins/build-tools/gbp-build-panel.ui
@@ -5,150 +5,57 @@
<property name="visible">true</property>
<property name="orientation">vertical</property>
<child>
- <object class="GtkGrid">
- <property name="border-width">12</property>
- <property name="column-spacing">12</property>
- <property name="column-homogeneous">true</property>
- <property name="row-spacing">3</property>
+ <object class="GtkMenuButton" id="configuration_button">
+ <property name="margin">12</property>
+ <property name="popover">configuration_popover</property>
<property name="visible">true</property>
+ <style>
+ <class name="flat"/>
+ </style>
<child>
- <object class="GtkLabel" id="label1">
- <property name="label" translatable="yes">Device:</property>
- <property name="xalign">1.0</property>
+ <object class="GtkLabel" id="configuration_label">
+ <property name="ellipsize">middle</property>
<property name="visible">true</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- <packing>
- <property name="left-attach">0</property>
- <property name="top-attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label2">
- <property name="label" translatable="yes">Framework:</property>
- <property name="xalign">1.0</property>
- <property name="visible">false</property>
- <style>
- <class name="dim-label"/>
- </style>
</object>
- <packing>
- <property name="left-attach">0</property>
- <property name="top-attach">1</property>
- </packing>
</child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">true</property>
+ <property name="margin-end">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="halign">end</property>
+ <property name="hexpand">true</property>
+ <property name="spacing">6</property>
+ <property name="homogeneous">true</property>
<child>
- <object class="GtkLabel" id="label3">
- <property name="label" translatable="yes">Packaging:</property>
- <property name="xalign">1.0</property>
- <property name="visible">false</property>
+ <object class="GtkButton">
+ <property name="action-name">build-tools.build</property>
+ <property name="label" translatable="yes">_Build</property>
+ <property name="use-underline">true</property>
+ <property name="visible">true</property>
<style>
- <class name="dim-label"/>
+ <class name="suggested-action"/>
</style>
</object>
- <packing>
- <property name="left-attach">0</property>
- <property name="top-attach">2</property>
- </packing>
</child>
<child>
- <object class="GtkBox">
+ <object class="GtkButton">
+ <property name="action-name">build-tools.rebuild</property>
+ <property name="label" translatable="yes">_Rebuild</property>
+ <property name="use-underline">true</property>
<property name="visible">true</property>
- <property name="margin-top">12</property>
- <property name="halign">end</property>
- <property name="hexpand">true</property>
- <property name="spacing">6</property>
- <property name="homogeneous">true</property>
- <child>
- <object class="GtkButton">
- <property name="action-name">build-tools.build</property>
- <property name="label" translatable="yes">_Build</property>
- <property name="use-underline">true</property>
- <property name="visible">true</property>
- <style>
- <class name="suggested-action"/>
- </style>
- </object>
- </child>
- <child>
- <object class="GtkButton">
- <property name="action-name">build-tools.rebuild</property>
- <property name="label" translatable="yes">_Rebuild</property>
- <property name="use-underline">true</property>
- <property name="visible">true</property>
- </object>
- </child>
- <child>
- <object class="GtkButton">
- <property name="action-name">build-tools.clean</property>
- <property name="label" translatable="yes">_Clean</property>
- <property name="use-underline">true</property>
- <property name="visible">true</property>
- </object>
- </child>
</object>
- <packing>
- <property name="left-attach">0</property>
- <property name="top-attach">3</property>
- <property name="width">2</property>
- </packing>
</child>
<child>
- <object class="GtkMenuButton" id="device_button">
- <property name="focus-on-click">false</property>
- <property name="popover">device_popover</property>
+ <object class="GtkButton">
+ <property name="action-name">build-tools.clean</property>
+ <property name="label" translatable="yes">_Clean</property>
+ <property name="use-underline">true</property>
<property name="visible">true</property>
- <style>
- <class name="flat"/>
- </style>
- <child>
- <object class="GtkBox">
- <property name="spacing">6</property>
- <property name="visible">true</property>
- <child>
- <object class="GtkLabel" id="device_label">
- <property name="visible">true</property>
- <property name="hexpand">false</property>
- <property name="xalign">0.0</property>
- </object>
- </child>
- <child>
- <object class="GtkImage">
- <property name="icon-name">pan-down-symbolic</property>
- <property name="visible">true</property>
- <property name="hexpand">false</property>
- <property name="xalign">0.0</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="left-attach">1</property>
- <property name="top-attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkMenuButton">
- <property name="label" translatable="yes">framework</property>
- <property name="visible">false</property>
- </object>
- <packing>
- <property name="left-attach">1</property>
- <property name="top-attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkMenuButton">
- <property name="label" translatable="yes">packaging</property>
- <property name="visible">false</property>
</object>
- <packing>
- <property name="left-attach">1</property>
- <property name="top-attach">2</property>
- </packing>
</child>
</object>
</child>
@@ -359,4 +266,23 @@
</object>
</child>
</object>
+ <object class="GtkPopover" id="configuration_popover">
+ <child>
+ <object class="EggScrolledWindow">
+ <property name="min-content-width">250</property>
+ <property name="max-content-width">300</property>
+ <property name="max-content-height">300</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkListBox" id="configurations">
+ <property name="selection-mode">none</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="buildpanel"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
</interface>
diff --git a/plugins/build-tools/gbp-build-perspective.c b/plugins/build-tools/gbp-build-perspective.c
new file mode 100644
index 0000000..28de2bc
--- /dev/null
+++ b/plugins/build-tools/gbp-build-perspective.c
@@ -0,0 +1,428 @@
+/* gbp-build-perspective.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "gbp-build-configuration-view.h"
+#include "gbp-build-perspective.h"
+
+struct _GbpBuildPerspective
+{
+ GtkBin parent_instance;
+
+ GActionGroup *actions;
+ IdeConfiguration *configuration;
+ IdeConfigurationManager *configuration_manager;
+
+ GtkListBox *list_box;
+ GbpBuildConfigurationView *view;
+};
+
+enum {
+ PROP_0,
+ PROP_CONFIGURATION,
+ PROP_CONFIGURATION_MANAGER,
+ LAST_PROP
+};
+
+static void perspective_iface_init (IdePerspectiveInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (GbpBuildPerspective, gbp_build_perspective, GTK_TYPE_BIN, 0,
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_PERSPECTIVE, perspective_iface_init))
+
+static GParamSpec *properties [LAST_PROP];
+
+static gboolean
+map_pointer_to (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ g_value_set_boolean (to_value, (user_data == g_value_get_object (from_value)));
+ return TRUE;
+}
+
+static GtkWidget *
+create_configuration_row (gpointer item,
+ gpointer user_data)
+{
+ IdeConfigurationManager *manager = user_data;
+ IdeConfiguration *configuration = item;
+ GtkWidget *row;
+ GtkWidget *box;
+ GtkWidget *label;
+ GtkWidget *image;
+
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
+
+ row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+ "visible", TRUE,
+ NULL);
+ g_object_set_data_full (G_OBJECT (row), "IDE_CONFIGURATION",
+ g_object_ref (configuration), g_object_unref);
+
+ box = g_object_new (GTK_TYPE_BOX,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "visible", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (row), box);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "ellipsize", PANGO_ELLIPSIZE_MIDDLE,
+ "visible", TRUE,
+ "xalign", 0.0f,
+ NULL);
+ g_object_bind_property (configuration, "display-name",
+ label, "label",
+ G_BINDING_SYNC_CREATE);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ image = g_object_new (GTK_TYPE_IMAGE,
+ "icon-name", "object-select-symbolic",
+ "xpad", 6,
+ NULL);
+ g_object_bind_property_full (manager, "current", image, "visible",
+ G_BINDING_SYNC_CREATE,
+ map_pointer_to, NULL, configuration, NULL);
+ gtk_container_add (GTK_CONTAINER (box), image);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "hexpand", TRUE,
+ "visible", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ return row;
+}
+
+static void
+gbp_build_perspective_set_configuration_manager (GbpBuildPerspective *self,
+ IdeConfigurationManager *manager)
+{
+ g_assert (GBP_IS_BUILD_PERSPECTIVE (self));
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
+
+ g_set_object (&self->configuration_manager, manager);
+ gtk_list_box_bind_model (self->list_box,
+ G_LIST_MODEL (manager),
+ create_configuration_row,
+ g_object_ref (manager),
+ g_object_unref);
+}
+
+static void
+gbp_build_perspective_row_selected (GbpBuildPerspective *self,
+ GtkListBoxRow *row,
+ GtkListBox *list_box)
+{
+ g_assert (GBP_IS_BUILD_PERSPECTIVE (self));
+ g_assert (!row || GTK_IS_LIST_BOX_ROW (row));
+ g_assert (GTK_IS_LIST_BOX (list_box));
+
+ if (row != NULL)
+ {
+ IdeConfiguration *configuration;
+
+ configuration = g_object_get_data (G_OBJECT (row), "IDE_CONFIGURATION");
+ g_set_object (&self->configuration, configuration);
+ gbp_build_configuration_view_set_configuration (self->view, configuration);
+ }
+}
+
+static void
+gbp_build_perspective_row_activated (GbpBuildPerspective *self,
+ GtkListBoxRow *row,
+ GtkListBox *list_box)
+{
+ IdeConfiguration *configuration;
+
+ g_assert (GBP_IS_BUILD_PERSPECTIVE (self));
+ g_assert (GTK_IS_LIST_BOX_ROW (row));
+ g_assert (GTK_IS_LIST_BOX (list_box));
+
+
+ configuration = g_object_get_data (G_OBJECT (row), "IDE_CONFIGURATION");
+ ide_configuration_manager_set_current (self->configuration_manager, configuration);
+}
+
+static void
+duplicate_configuration (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ GbpBuildPerspective *self = user_data;
+
+ g_assert (GBP_IS_BUILD_PERSPECTIVE (self));
+
+ if (self->configuration != NULL)
+ {
+ g_autoptr(IdeConfiguration) copy = NULL;
+
+ copy = ide_configuration_duplicate (self->configuration);
+ ide_configuration_manager_add (self->configuration_manager, copy);
+ }
+}
+
+static void
+delete_configuration (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ GbpBuildPerspective *self = user_data;
+
+ g_assert (GBP_IS_BUILD_PERSPECTIVE (self));
+
+ if (self->configuration != NULL)
+ {
+ g_autoptr(IdeConfiguration) config = NULL;
+
+ /*
+ * Make sure we hold onto a reference during the call, as it is likely
+ * self->configuration will change during this call.
+ */
+ config = g_object_ref (self->configuration);
+ ide_configuration_manager_remove (self->configuration_manager, config);
+
+ /*
+ * Switch to the first configuration in the list. The configuration
+ * manager should have added a new "default" configuration if we
+ * deleted the last configuration, so we should just get the 0th
+ * index.
+ */
+ if (g_list_model_get_n_items (G_LIST_MODEL (self->configuration_manager)) > 0)
+ {
+ g_autoptr(IdeConfiguration) first = NULL;
+
+ first = g_list_model_get_item (G_LIST_MODEL (self->configuration_manager), 0);
+ gbp_build_perspective_set_configuration (self, first);
+ }
+ }
+}
+
+static void
+gbp_build_perspective_finalize (GObject *object)
+{
+ GbpBuildPerspective *self = (GbpBuildPerspective *)object;
+
+ g_clear_object (&self->actions);
+ g_clear_object (&self->configuration);
+
+ G_OBJECT_CLASS (gbp_build_perspective_parent_class)->finalize (object);
+}
+
+static void
+gbp_build_perspective_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbpBuildPerspective *self = GBP_BUILD_PERSPECTIVE (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIGURATION:
+ g_value_set_object (value, gbp_build_perspective_get_configuration (self));
+ break;
+
+ case PROP_CONFIGURATION_MANAGER:
+ g_value_set_object (value, self->configuration_manager);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gbp_build_perspective_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbpBuildPerspective *self = GBP_BUILD_PERSPECTIVE (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIGURATION:
+ gbp_build_perspective_set_configuration (self, g_value_get_object (value));
+ break;
+
+ case PROP_CONFIGURATION_MANAGER:
+ gbp_build_perspective_set_configuration_manager (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gbp_build_perspective_class_init (GbpBuildPerspectiveClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gbp_build_perspective_finalize;
+ object_class->get_property = gbp_build_perspective_get_property;
+ object_class->set_property = gbp_build_perspective_set_property;
+
+ properties [PROP_CONFIGURATION_MANAGER] =
+ g_param_spec_object ("configuration-manager",
+ "Configuration Manager",
+ "Configuration Manager",
+ IDE_TYPE_CONFIGURATION_MANAGER,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_CONFIGURATION] =
+ g_param_spec_object ("configuration",
+ "Configuration",
+ "The configuration to edit",
+ IDE_TYPE_CONFIGURATION,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/builder/plugins/build-tools-plugin/gbp-build-perspective.ui");
+ gtk_widget_class_set_css_name (widget_class, "buildperspective");
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildPerspective, list_box);
+ gtk_widget_class_bind_template_child (widget_class, GbpBuildPerspective, view);
+
+ g_type_ensure (GBP_TYPE_BUILD_CONFIGURATION_VIEW);
+}
+
+static void
+gbp_build_perspective_init (GbpBuildPerspective *self)
+{
+ static GActionEntry actions[] = {
+ { "delete-configuration", delete_configuration },
+ { "duplicate-configuration", duplicate_configuration },
+ };
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect_object (self->list_box,
+ "row-selected",
+ G_CALLBACK (gbp_build_perspective_row_selected),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->list_box,
+ "row-activated",
+ G_CALLBACK (gbp_build_perspective_row_activated),
+ self,
+ G_CONNECT_SWAPPED);
+
+ self->actions = G_ACTION_GROUP (g_simple_action_group_new ());
+ g_action_map_add_action_entries (G_ACTION_MAP (self->actions), actions,
+ G_N_ELEMENTS (actions), self);
+}
+
+GtkWidget *
+gbp_build_perspective_new (void)
+{
+ return g_object_new (GBP_TYPE_BUILD_PERSPECTIVE, NULL);
+}
+
+IdeConfiguration *
+gbp_build_perspective_get_configuration (GbpBuildPerspective *self)
+{
+ g_return_val_if_fail (GBP_IS_BUILD_PERSPECTIVE (self), NULL);
+
+ return self->configuration;
+}
+
+static void
+find_configuration_row (GtkWidget *widget,
+ gpointer data)
+{
+ struct {
+ IdeConfiguration *config;
+ GtkWidget *row;
+ } *lookup = data;
+
+ if (lookup->row != NULL)
+ return;
+
+ if (lookup->config == g_object_get_data (G_OBJECT (widget), "IDE_CONFIGURATION"))
+ lookup->row = widget;
+}
+
+void
+gbp_build_perspective_set_configuration (GbpBuildPerspective *self,
+ IdeConfiguration *configuration)
+{
+ struct {
+ IdeConfiguration *config;
+ GtkWidget *row;
+ } lookup = { configuration, NULL };
+
+ g_return_if_fail (GBP_IS_BUILD_PERSPECTIVE (self));
+ g_return_if_fail (!configuration || IDE_IS_CONFIGURATION (configuration));
+
+ gtk_container_foreach (GTK_CONTAINER (self->list_box),
+ find_configuration_row,
+ &lookup);
+
+ if (GTK_IS_LIST_BOX_ROW (lookup.row))
+ gtk_list_box_select_row (self->list_box, GTK_LIST_BOX_ROW (lookup.row));
+}
+
+static gchar *
+gbp_build_perspective_get_icon_name (IdePerspective *perspective)
+{
+ return g_strdup ("builder-build-configure-symbolic");
+}
+
+static gchar *
+gbp_build_perspective_get_title (IdePerspective *perspective)
+{
+ return g_strdup ("Build Configuration");
+}
+
+static gchar *
+gbp_build_perspective_get_id (IdePerspective *perspective)
+{
+ return g_strdup ("buildperspective");
+}
+
+static gint
+gbp_build_perspective_get_priority (IdePerspective *perspective)
+{
+ return 80000;
+}
+
+static GActionGroup *
+gbp_build_perspective_get_actions (IdePerspective *perspective)
+{
+ GbpBuildPerspective *self = (GbpBuildPerspective *)perspective;
+
+ g_assert (GBP_IS_BUILD_PERSPECTIVE (self));
+
+ return g_object_ref (self->actions);
+}
+
+static void
+perspective_iface_init (IdePerspectiveInterface *iface)
+{
+ iface->get_actions = gbp_build_perspective_get_actions;
+ iface->get_icon_name = gbp_build_perspective_get_icon_name;
+ iface->get_title = gbp_build_perspective_get_title;
+ iface->get_id = gbp_build_perspective_get_id;
+ iface->get_priority = gbp_build_perspective_get_priority;
+}
diff --git a/plugins/build-tools/gbp-build-perspective.h b/plugins/build-tools/gbp-build-perspective.h
new file mode 100644
index 0000000..22242f7
--- /dev/null
+++ b/plugins/build-tools/gbp-build-perspective.h
@@ -0,0 +1,36 @@
+/* gbp-build-perspective.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GBP_BUILD_PERSPECTIVE_H
+#define GBP_BUILD_PERSPECTIVE_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_BUILD_PERSPECTIVE (gbp_build_perspective_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpBuildPerspective, gbp_build_perspective, GBP, BUILD_PERSPECTIVE, GtkBin)
+
+IdeConfiguration *gbp_build_perspective_get_configuration (GbpBuildPerspective *self);
+void gbp_build_perspective_set_configuration (GbpBuildPerspective *self,
+ IdeConfiguration *configuration);
+
+G_END_DECLS
+
+#endif /* GBP_BUILD_PERSPECTIVE_H */
diff --git a/plugins/build-tools/gbp-build-perspective.ui b/plugins/build-tools/gbp-build-perspective.ui
new file mode 100644
index 0000000..da4490a
--- /dev/null
+++ b/plugins/build-tools/gbp-build-perspective.ui
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.18 -->
+ <template class="GbpBuildPerspective" parent="GtkBin">
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkListBox" id="list_box">
+ <property name="selection-mode">browse</property>
+ <property name="activate-on-single-click">false</property>
+ <property name="width-request">200</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="sidebar"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GbpBuildConfigurationView" id="view">
+ <property name="expand">true</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="IdeWorkbenchHeaderBar" id="titlebar">
+ <property name="visible">true</property>
+ <child type="title">
+ <object class="GtkSearchEntry" id="search_entry">
+ <property name="hexpand">true</property>
+ <property name="margin-end">6</property>
+ <property name="margin-start">6</property>
+ <property name="max-width-chars">50</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/plugins/build-tools/gbp-build-tool.c b/plugins/build-tools/gbp-build-tool.c
index b24dea6..a8d3a0e 100644
--- a/plugins/build-tools/gbp-build-tool.c
+++ b/plugins/build-tools/gbp-build-tool.c
@@ -30,6 +30,8 @@ struct _GbpBuildTool
gint64 build_start;
};
+static gint parallel = -1;
+
static void application_tool_init (IdeApplicationToolInterface *iface);
G_DEFINE_TYPE_EXTENDED (GbpBuildTool, gbp_build_tool, G_TYPE_OBJECT, 0,
@@ -155,12 +157,15 @@ gbp_build_tool_new_context_cb (GObject *object,
g_autoptr(IdeBuilder) builder = NULL;
g_autoptr(IdeBuildResult) build_result = NULL;
g_autoptr(IdeDevice) device = NULL;
+ g_autoptr(IdeConfiguration) configuration = NULL;
IdeDeviceManager *device_manager;
+ IdeRuntimeManager *runtime_manager;
IdeBuildSystem *build_system;
GbpBuildTool *self;
IdeBuilderBuildFlags flags;
- GKeyFile *config;
+ IdeRuntime *runtime;
const gchar *device_id;
+ const gchar *runtime_id;
GError *error = NULL;
g_assert (G_IS_TASK (task));
@@ -175,7 +180,6 @@ gbp_build_tool_new_context_cb (GObject *object,
return;
}
- config = g_object_get_data (G_OBJECT (task), "CONFIG");
flags = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task), "FLAGS"));
device_id = g_object_get_data (G_OBJECT (task), "DEVICE_ID");
@@ -193,12 +197,34 @@ gbp_build_tool_new_context_cb (GObject *object,
return;
}
+ runtime_id = g_object_get_data (G_OBJECT (task), "RUNTIME_ID");
+ runtime_manager = ide_context_get_runtime_manager (context);
+ runtime = ide_runtime_manager_get_runtime (runtime_manager, runtime_id);
+
+ if (runtime == NULL)
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND,
+ _("Failed to locate runtime \"%s\""),
+ runtime_id);
+ return;
+ }
+
+ configuration = ide_configuration_new (context, "command-line-build", device_id, runtime_id);
+
+ if (parallel > -1)
+ {
+ g_autofree gchar *str = g_strdup_printf ("%d", parallel);
+ ide_configuration_setenv (configuration, "PARALLEL", str);
+ }
+
print_build_info (context, device);
/* TODO: Support custom configs */
build_system = ide_context_get_build_system (context);
- builder = ide_build_system_get_builder (build_system, config, device, &error);
+ builder = ide_build_system_get_builder (build_system, configuration, &error);
if (builder == NULL)
{
@@ -241,12 +267,11 @@ gbp_build_tool_run_async (IdeApplicationTool *tool,
g_autoptr(GTask) task = NULL;
g_autofree gchar *project_path = NULL;
g_autofree gchar *device_id = NULL;
+ g_autofree gchar *runtime_id = NULL;
g_autoptr(GFile) project_file = NULL;
g_autoptr(GOptionContext) opt_context = NULL;
- g_autoptr(GKeyFile) config = NULL;
g_auto(GStrv) strv = NULL;
gboolean clean = FALSE;
- gint parallel = -1;
IdeBuilderBuildFlags flags = 0;
GError *error = NULL;
const GOptionEntry entries[] = {
@@ -255,6 +280,9 @@ gbp_build_tool_run_async (IdeApplicationTool *tool,
{ "device", 'd', 0, G_OPTION_ARG_STRING, &device_id,
N_("The ID of the device to build for"),
N_("local") },
+ { "runtime", 'd', 0, G_OPTION_ARG_STRING, &runtime_id,
+ N_("The runtime to use for building"),
+ N_("host") },
{ "parallel", 'j', 0, G_OPTION_ARG_INT, ¶llel,
N_("Number of workers to use when building"),
N_("N") },
@@ -288,16 +316,14 @@ gbp_build_tool_run_async (IdeApplicationTool *tool,
if (device_id == NULL)
device_id = g_strdup ("local");
- config = g_key_file_new ();
-
- if (parallel >= -1)
- g_key_file_set_integer (config, "parallel", "workers", parallel);
-
if (clean)
- flags |= IDE_BUILDER_BUILD_FLAGS_CLEAN;
+ {
+ flags |= IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN;
+ flags |= IDE_BUILDER_BUILD_FLAGS_NO_BUILD;
+ }
g_object_set_data_full (G_OBJECT (task), "DEVICE_ID", g_strdup (device_id), g_free);
- g_object_set_data_full (G_OBJECT (task), "CONFIG", g_key_file_ref (config),
(GDestroyNotify)g_key_file_unref);
+ g_object_set_data_full (G_OBJECT (task), "RUNTIME_ID", g_strdup (runtime_id ?: "host"), g_free);
g_object_set_data (G_OBJECT (task), "FLAGS", GINT_TO_POINTER (flags));
ide_context_new_async (project_file,
diff --git a/plugins/build-tools/gbp-build-tools.gresource.xml
b/plugins/build-tools/gbp-build-tools.gresource.xml
index fa21ac1..7204608 100644
--- a/plugins/build-tools/gbp-build-tools.gresource.xml
+++ b/plugins/build-tools/gbp-build-tools.gresource.xml
@@ -2,9 +2,14 @@
<gresources>
<gresource prefix="/org/gnome/builder/plugins/build-tools-plugin">
<file>gtk/menus.ui</file>
- <file>theme/shared.css</file>
+ <file>theme/Adwaita.css</file>
+ <file>theme/Adwaita-dark.css</file>
+ <file>theme/Adwaita-shared.css</file>
+ <file>gbp-build-configuration-row.ui</file>
+ <file>gbp-build-configuration-view.ui</file>
<file>gbp-build-log-panel.ui</file>
- <file>gbp-build-panel.ui</file>
<file>gbp-build-panel-row.ui</file>
+ <file>gbp-build-panel.ui</file>
+ <file>gbp-build-perspective.ui</file>
</gresource>
</gresources>
diff --git a/plugins/build-tools/gbp-build-workbench-addin.c b/plugins/build-tools/gbp-build-workbench-addin.c
index df558f8..52369dc 100644
--- a/plugins/build-tools/gbp-build-workbench-addin.c
+++ b/plugins/build-tools/gbp-build-workbench-addin.c
@@ -20,8 +20,9 @@
#include "egg-binding-group.h"
-#include "gbp-build-panel.h"
#include "gbp-build-log-panel.h"
+#include "gbp-build-panel.h"
+#include "gbp-build-perspective.h"
#include "gbp-build-workbench-addin.h"
struct _GbpBuildWorkbenchAddin
@@ -38,18 +39,15 @@ struct _GbpBuildWorkbenchAddin
IdeBuildResult *result;
GSimpleActionGroup *actions;
GCancellable *cancellable;
- IdeDevice *device;
};
static void workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface);
G_DEFINE_TYPE_EXTENDED (GbpBuildWorkbenchAddin, gbp_build_workbench_addin, G_TYPE_OBJECT, 0,
- G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN,
- workbench_addin_iface_init))
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN, workbench_addin_iface_init))
enum {
PROP_0,
- PROP_DEVICE,
PROP_RESULT,
LAST_PROP
};
@@ -57,25 +55,6 @@ enum {
static GParamSpec *properties [LAST_PROP];
static void
-gbp_build_workbench_addin_set_device (GbpBuildWorkbenchAddin *self,
- IdeDevice *device)
-{
- g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
- g_assert (IDE_IS_DEVICE (device));
-
- if (g_set_object (&self->device, device))
- {
- const gchar *id = ide_device_get_id (device);
- GAction *action;
-
- action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), "device");
- g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (id));
-
- g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEVICE]);
- }
-}
-
-static void
gbp_build_workbench_addin_set_result (GbpBuildWorkbenchAddin *self,
IdeBuildResult *result)
{
@@ -148,6 +127,8 @@ gbp_build_workbench_addin_do_build (GbpBuildWorkbenchAddin *self,
{
g_autoptr(IdeBuilder) builder = NULL;
g_autoptr(GError) error = NULL;
+ IdeConfigurationManager *config_manager;
+ IdeConfiguration *configuration;
IdeBuildSystem *build_system;
IdeWorkbench *workbench;
IdeContext *context;
@@ -164,7 +145,10 @@ gbp_build_workbench_addin_do_build (GbpBuildWorkbenchAddin *self,
workbench = ide_widget_get_workbench (GTK_WIDGET (self->panel));
context = ide_workbench_get_context (workbench);
build_system = ide_context_get_build_system (context);
- builder = ide_build_system_get_builder (build_system, NULL, self->device, &error);
+ config_manager = ide_context_get_configuration_manager (context);
+ configuration = ide_configuration_manager_get_current (config_manager);
+
+ builder = ide_build_system_get_builder (build_system, configuration, &error);
if (error != NULL)
{
@@ -217,7 +201,7 @@ gbp_build_workbench_addin_rebuild (GSimpleAction *action,
g_assert (G_IS_SIMPLE_ACTION (action));
g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
- gbp_build_workbench_addin_do_build (self, IDE_BUILDER_BUILD_FLAGS_FORCE_REBUILD);
+ gbp_build_workbench_addin_do_build (self, IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN);
}
static void
@@ -230,7 +214,9 @@ gbp_build_workbench_addin_clean (GSimpleAction *action,
g_assert (G_IS_SIMPLE_ACTION (action));
g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
- gbp_build_workbench_addin_do_build (self, IDE_BUILDER_BUILD_FLAGS_CLEAN);
+ gbp_build_workbench_addin_do_build (self,
+ (IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN |
+ IDE_BUILDER_BUILD_FLAGS_NO_BUILD));
}
static void
@@ -246,68 +232,24 @@ gbp_build_workbench_addin_cancel (GSimpleAction *action,
g_cancellable_cancel (self->cancellable);
}
-static void
-gbp_build_workbench_addin_deploy (GSimpleAction *action,
- GVariant *param,
- gpointer user_data)
-{
-}
-
-static void
-gbp_build_workbench_addin_export (GSimpleAction *action,
- GVariant *param,
- gpointer user_data)
-{
-}
-
-static void
-gbp_build_workbench_addin_device (GSimpleAction *action,
- GVariant *param,
- gpointer user_data)
-{
- GbpBuildWorkbenchAddin *self = user_data;
- IdeDeviceManager *device_manager;
- IdeContext *context;
- IdeDevice *device;
- const gchar *id;
-
- g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
- g_assert (IDE_IS_WORKBENCH (self->workbench));
-
- id = g_variant_get_string (param, NULL);
- if (id == NULL)
- id = "local";
-
- context = ide_workbench_get_context (self->workbench);
- device_manager = ide_context_get_device_manager (context);
- device = ide_device_manager_get_device (device_manager, id);
-
- if (device == NULL)
- device = ide_device_manager_get_device (device_manager, "local");
-
- gbp_build_workbench_addin_set_device (self, device);
-}
-
static const GActionEntry actions[] = {
{ "build", gbp_build_workbench_addin_build },
{ "rebuild", gbp_build_workbench_addin_rebuild },
{ "clean", gbp_build_workbench_addin_clean },
{ "cancel-build", gbp_build_workbench_addin_cancel },
- { "deploy", gbp_build_workbench_addin_deploy },
- { "export", gbp_build_workbench_addin_export },
- { "device", NULL, "s", "'local'", gbp_build_workbench_addin_device },
};
static void
gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
IdeWorkbench *workbench)
{
+ IdeConfigurationManager *configuration_manager;
GbpBuildWorkbenchAddin *self = (GbpBuildWorkbenchAddin *)addin;
+ IdeConfiguration *configuration;
IdePerspective *editor;
- GtkWidget *pane;
IdeContext *context;
- IdeDeviceManager *device_manager;
- IdeDevice *device;
+ GtkWidget *build_perspective;
+ GtkWidget *pane;
g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
@@ -316,33 +258,33 @@ gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
self->workbench = workbench;
context = ide_workbench_get_context (workbench);
- device_manager = ide_context_get_device_manager (context);
- device = ide_device_manager_get_device (device_manager, "local");
+ configuration_manager = ide_context_get_configuration_manager (context);
+ configuration = ide_configuration_manager_get_current (configuration_manager);
editor = ide_workbench_get_perspective_by_name (workbench, "editor");
pane = ide_layout_get_right_pane (IDE_LAYOUT (editor));
self->panel = g_object_new (GBP_TYPE_BUILD_PANEL,
- "device", device,
- "device-manager", device_manager,
+ "configuration-manager", configuration_manager,
"visible", TRUE,
NULL);
- ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane),
- GTK_WIDGET (self->panel),
- _("Build"), NULL);
+ ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->panel), _("Build"), NULL);
pane = ide_layout_get_bottom_pane (IDE_LAYOUT (editor));
self->build_log_panel = g_object_new (GBP_TYPE_BUILD_LOG_PANEL, NULL);
- ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane),
- GTK_WIDGET (self->build_log_panel),
+ ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->build_log_panel),
_("Build Output"), NULL);
gtk_widget_insert_action_group (GTK_WIDGET (workbench), "build-tools",
G_ACTION_GROUP (self->actions));
g_object_bind_property (self, "result", self->panel, "result", 0);
- g_object_bind_property (self, "device", self->panel, "device", 0);
- gbp_build_workbench_addin_set_device (self, device);
+ build_perspective = g_object_new (GBP_TYPE_BUILD_PERSPECTIVE,
+ "configuration-manager", configuration_manager,
+ "configuration", configuration,
+ "visible", TRUE,
+ NULL);
+ ide_workbench_add_perspective (workbench, IDE_PERSPECTIVE (build_perspective));
}
static void
@@ -379,10 +321,6 @@ gbp_build_workbench_addin_get_property (GObject *object,
switch (prop_id)
{
- case PROP_DEVICE:
- g_value_set_object (value, self->device);
- break;
-
case PROP_RESULT:
g_value_set_object (value, self->result);
break;
@@ -393,30 +331,10 @@ gbp_build_workbench_addin_get_property (GObject *object,
}
static void
-gbp_build_workbench_addin_set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- GbpBuildWorkbenchAddin *self = GBP_BUILD_WORKBENCH_ADDIN(object);
-
- switch (prop_id)
- {
- case PROP_DEVICE:
- gbp_build_workbench_addin_set_device (self, g_value_get_object (value));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
- }
-}
-
-static void
gbp_build_workbench_addin_finalize (GObject *object)
{
GbpBuildWorkbenchAddin *self = (GbpBuildWorkbenchAddin *)object;
- g_clear_object (&self->device);
g_clear_object (&self->bindings);
g_clear_object (&self->actions);
g_clear_object (&self->result);
@@ -432,14 +350,6 @@ gbp_build_workbench_addin_class_init (GbpBuildWorkbenchAddinClass *klass)
object_class->finalize = gbp_build_workbench_addin_finalize;
object_class->get_property = gbp_build_workbench_addin_get_property;
- object_class->set_property = gbp_build_workbench_addin_set_property;
-
- properties [PROP_DEVICE] =
- g_param_spec_object ("device",
- "Device",
- "The device the build is for",
- IDE_TYPE_DEVICE,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_RESULT] =
g_param_spec_object ("result",
diff --git a/plugins/build-tools/theme/Adwaita-dark.css b/plugins/build-tools/theme/Adwaita-dark.css
new file mode 100644
index 0000000..1fea00a
--- /dev/null
+++ b/plugins/build-tools/theme/Adwaita-dark.css
@@ -0,0 +1 @@
+ import url("resource:///org/gnome/builder/plugins/build-tools-plugin/theme/Adwaita-shared.css");
diff --git a/plugins/build-tools/theme/Adwaita-shared.css b/plugins/build-tools/theme/Adwaita-shared.css
new file mode 100644
index 0000000..834fd14
--- /dev/null
+++ b/plugins/build-tools/theme/Adwaita-shared.css
@@ -0,0 +1,82 @@
+/*
+buildpanel button.popup:active,
+buildpanel button.popup:checked,
+buildpanel button.popup {
+ background-image: none;
+ background-color: transparent;
+ box-shadow: none;
+ border-style: none;
+ margin: 0;
+ padding: 0;
+}
+*/
+
+list.buildpanel row {
+ padding: 10px;
+ border-bottom: 1px solid alpha(@borders, 0.4);
+}
+
+list.buildpanel row:last-child {
+ border-bottom: none;
+}
+
+buildpanel list row {
+ padding: 6px;
+ border-bottom: 1px solid alpha(@borders, 0.4);
+}
+
+buildpanel list row:last-child {
+ border-bottom: none;
+}
+
+buildpanel list row label.file {
+ opacity: 0.5;
+}
+
+/*
+buildpanel list row image {
+ opacity: 0.5;
+}
+*/
+
+buildlogpanel textview {
+ padding: 3px;
+}
+
+buildpanel box.build-status {
+ background: shade(@theme_bg_color, 0.96);
+}
+
+
+buildperspective list.sidebar row {
+ padding: 10px;
+ border-bottom: 1px solid alpha(@borders, 0.2);
+}
+buildperspective list.sidebar row:last-child {
+ border-bottom: none;
+}
+buildperspective list.sidebar {
+ background: @theme_base_color;
+ border-right: 1px solid alpha(@borders, 0.4);
+}
+
+
+configurationview list row {
+ padding: 10px;
+ border-bottom: 1px solid alpha(@borders, 0.4);
+}
+configurationview list row:last-child {
+ border-bottom: none;
+}
+configurationview list row entry {
+ background: transparent;
+ border: none;
+ padding: 0;
+ margin: 0;
+}
+
+
+/* hrmm, we can use this to get row separators */
+configurationview treeview {
+ border-bottom: 1px solid alpha(@borders, 0.4);
+}
diff --git a/plugins/build-tools/theme/Adwaita.css b/plugins/build-tools/theme/Adwaita.css
new file mode 100644
index 0000000..1fea00a
--- /dev/null
+++ b/plugins/build-tools/theme/Adwaita.css
@@ -0,0 +1 @@
+ import url("resource:///org/gnome/builder/plugins/build-tools-plugin/theme/Adwaita-shared.css");
diff --git a/plugins/vala-pack/Makefile.am b/plugins/vala-pack/Makefile.am
index 6457edb..5f17f1d 100644
--- a/plugins/vala-pack/Makefile.am
+++ b/plugins/vala-pack/Makefile.am
@@ -27,6 +27,7 @@ libvala_pack_plugin_la_VALAFLAGS = \
--target-glib=2.44 \
--thread \
--vapidir $(top_builddir)/libide \
+ --vapidir $(top_builddir)/contrib/egg \
--vapidir $(top_builddir)/contrib/tmpl \
--pkg gtksourceview-3.0 \
--pkg libide-1.0 \
@@ -44,6 +45,13 @@ libvala_pack_plugin_la_CFLAGS = \
-I$(top_srcdir)/libide \
-I$(top_srcdir)/contrib/egg \
-I$(top_srcdir)/contrib/tmpl \
+ -Wno-deprecated-declarations \
+ -Wno-discarded-qualifiers \
+ -Wno-implicit-function-declaration \
+ -Wno-incompatible-pointer-types \
+ -Wno-unused-but-set-variable \
+ -Wno-unused-label \
+ -Wno-unused-function \
$(NULL)
libvala_pack_plugin_la_LDFLAGS = \
diff --git a/plugins/vala-pack/ide-vala-index.vala b/plugins/vala-pack/ide-vala-index.vala
index 2640b56..d50de2c 100644
--- a/plugins/vala-pack/ide-vala-index.vala
+++ b/plugins/vala-pack/ide-vala-index.vala
@@ -65,8 +65,8 @@ namespace Ide
this.code_context.compile_only = true;
this.code_context.use_header = false;
this.code_context.includedir = null;
- this.code_context.basedir = Environment.get_current_dir ();
- this.code_context.directory = Environment.get_current_dir ();
+ this.code_context.basedir = GLib.Environment.get_current_dir ();
+ this.code_context.directory = GLib.Environment.get_current_dir ();
this.code_context.debug = false;
this.code_context.thread = true;
this.code_context.mem_profiler = false;
diff --git a/plugins/xdg-app/Makefile.am b/plugins/xdg-app/Makefile.am
new file mode 100644
index 0000000..4300f6b
--- /dev/null
+++ b/plugins/xdg-app/Makefile.am
@@ -0,0 +1,40 @@
+if ENABLE_XDG_APP_PLUGIN
+
+DISTCLEANFILES =
+BUILT_SOURCES =
+CLEANFILES =
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_LTLIBRARIES = libxdg-app-plugin.la
+dist_plugin_DATA = xdg-app.plugin
+
+libxdg_app_plugin_la_SOURCES = \
+ gbp-xdg-runtime-provider.c \
+ gbp-xdg-runtime-provider.h \
+ gbp-xdg-runtime.c \
+ gbp-xdg-runtime.h \
+ gbp-xdg-plugin.c \
+ $(NULL)
+
+libxdg_app_plugin_la_CFLAGS = \
+ $(LIBIDE_CFLAGS) \
+ $(XDG_APP_CFLAGS) \
+ $(OPTIMIZE_CFLAGS) \
+ -I$(top_srcdir)/libide \
+ $(NULL)
+
+libxdg_app_plugin_la_LIBADD = $(XDG_APP_LIBS)
+
+libxdg_app_plugin_la_LDFLAGS = \
+ $(OPTIMIZE_LDFLAGS) \
+ -avoid-version \
+ -module \
+ -export-regex peas_register_types \
+ $(NULL)
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/xdg-app/configure.ac b/plugins/xdg-app/configure.ac
new file mode 100644
index 0000000..53875d6
--- /dev/null
+++ b/plugins/xdg-app/configure.ac
@@ -0,0 +1,24 @@
+m4_define([xdg_app_required_version], [0.4.8])
+
+# --enable-xdg-app-plugin=yes/no/auto
+AC_ARG_ENABLE([xdg-app-plugin],
+ [AS_HELP_STRING([--enable-xdg-app-plugin=@<:@yes/no/auto@:>@],
+ [Build with support for xdg-app.])],
+ [enable_xdg_app_plugin=$enableval],
+ [enable_xdg_app_plugin=auto])
+
+AS_IF([test "$enable_xdg_app_plugin" != no],[
+ PKG_CHECK_MODULES(XDG_APP,
+ [xdg-app >= xdg_app_required_version],
+ [have_xdg_app=yes],
+ [have_xdg_app=no])
+
+ AS_IF([test "$enable_xdg_app_plugin" = "yes" && "$have_xdg_app" = "no"],[
+ AC_MSG_ERROR([--enable-xdg-app-plugin requires xdg-app >= xdg_app_required_version])
+ ])
+
+ enable_xdg_app_plugin=yes
+])
+
+AM_CONDITIONAL(ENABLE_XDG_APP_PLUGIN, test x$enable_xdg_app_plugin = xyes)
+AC_CONFIG_FILES([plugins/xdg-app/Makefile])
diff --git a/plugins/xdg-app/gbp-xdg-plugin.c b/plugins/xdg-app/gbp-xdg-plugin.c
new file mode 100644
index 0000000..5fb941f
--- /dev/null
+++ b/plugins/xdg-app/gbp-xdg-plugin.c
@@ -0,0 +1,30 @@
+/* gbp-xdg-plugin.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libpeas/peas.h>
+#include <ide.h>
+
+#include "gbp-xdg-runtime-provider.h"
+
+void
+peas_register_types (PeasObjectModule *module)
+{
+ peas_object_module_register_extension_type (module,
+ IDE_TYPE_RUNTIME_PROVIDER,
+ GBP_TYPE_XDG_RUNTIME_PROVIDER);
+}
diff --git a/plugins/xdg-app/gbp-xdg-runtime-provider.c b/plugins/xdg-app/gbp-xdg-runtime-provider.c
new file mode 100644
index 0000000..3746c75
--- /dev/null
+++ b/plugins/xdg-app/gbp-xdg-runtime-provider.c
@@ -0,0 +1,250 @@
+/* gbp-xdg-runtime-provider.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <xdg-app.h>
+
+#include "util/ide-posix.h"
+
+#include "gbp-xdg-runtime.h"
+#include "gbp-xdg-runtime-provider.h"
+
+struct _GbpXdgRuntimeProvider
+{
+ GObject parent_instance;
+ IdeRuntimeManager *manager;
+ XdgAppInstallation *installation;
+ GCancellable *cancellable;
+ GPtrArray *runtimes;
+};
+
+static void runtime_provider_iface_init (IdeRuntimeProviderInterface *);
+
+G_DEFINE_TYPE_EXTENDED (GbpXdgRuntimeProvider, gbp_xdg_runtime_provider, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_RUNTIME_PROVIDER,
+ runtime_provider_iface_init))
+
+static inline void
+sanitize_name (gchar *name)
+{
+ gchar *tmp = strchr (name, '/');
+
+ if (tmp != NULL)
+ *tmp = '\0';
+}
+
+static void
+gbp_xdg_runtime_provider_load_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GbpXdgRuntimeProvider *self = source_object;
+ g_autofree gchar *host_type = NULL;
+ IdeContext *context;
+ GPtrArray *ret;
+ GPtrArray *ar;
+ GError *error = NULL;
+ guint i;
+
+ g_assert (G_IS_TASK (task));
+ g_assert (GBP_IS_XDG_RUNTIME_PROVIDER (self));
+ g_assert (IDE_IS_RUNTIME_MANAGER (self->manager));
+
+ context = ide_object_get_context (IDE_OBJECT (self->manager));
+ host_type = ide_get_system_arch ();
+
+ self->installation = xdg_app_installation_new_user (cancellable, &error);
+
+ if (self->installation == NULL)
+ {
+ g_task_return_error (task, error);
+ return;
+ }
+
+ ar = xdg_app_installation_list_installed_refs_by_kind (self->installation,
+ XDG_APP_REF_KIND_RUNTIME,
+ cancellable,
+ &error);
+
+ if (ar == NULL)
+ {
+ g_task_return_error (task, error);
+ return;
+ }
+
+ ret = g_ptr_array_new_with_free_func (g_object_unref);
+
+ for (i = 0; i < ar->len; i++)
+ {
+ XdgAppInstalledRef *ref = g_ptr_array_index (ar, i);
+ g_autofree gchar *str = NULL;
+ g_autofree gchar *id = NULL;
+ g_autofree gchar *name = NULL;
+ const gchar *arch;
+ const gchar *branch;
+ g_autofree gchar *metadata = NULL;
+ g_autofree gchar *sdk = NULL;
+ g_autoptr(GKeyFile) key_file = NULL;
+
+ g_assert (XDG_APP_IS_INSTALLED_REF (ref));
+
+ name = g_strdup (xdg_app_ref_get_name (XDG_APP_REF (ref)));
+
+ sanitize_name (name);
+
+ arch = xdg_app_ref_get_arch (XDG_APP_REF (ref));
+ branch = xdg_app_ref_get_branch (XDG_APP_REF (ref));
+
+ id = g_strdup_printf ("xdg-app:%s/%s/%s", name, branch, arch);
+
+ if (g_strcmp0 (host_type, arch) == 0)
+ str = g_strdup_printf ("%s <b>%s</b>", name, branch);
+ else
+ str = g_strdup_printf ("%s <b>%s</b> <sup>%s</sup>", name, branch, arch);
+
+ metadata = xdg_app_installed_ref_load_metadata (XDG_APP_INSTALLED_REF (ref),
+ cancellable, &error);
+
+ if (metadata == NULL)
+ {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ continue;
+ }
+
+ key_file = g_key_file_new ();
+
+ if (!g_key_file_load_from_data (key_file, metadata, -1, G_KEY_FILE_NONE, &error))
+ {
+ /*
+ * If this is not really a runtime, but something like a locale, then
+ * the metadata file will not exist.
+ */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ g_clear_error (&error);
+ continue;
+ }
+
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ continue;
+ }
+
+ if (!(sdk = g_key_file_get_string (key_file, "Runtime", "sdk", NULL)))
+ sdk = g_strdup (name);
+
+ sanitize_name (sdk);
+
+ g_ptr_array_add (ret,
+ g_object_new (GBP_TYPE_XDG_RUNTIME,
+ "branch", branch,
+ "sdk", sdk,
+ "platform", name,
+ "context", context,
+ "id", id,
+ "display-name", str,
+ NULL));
+ }
+
+ g_ptr_array_unref (ar);
+
+ g_task_return_pointer (task, ret, (GDestroyNotify)g_ptr_array_unref);
+}
+
+static void
+gbp_xdg_runtime_provider_load_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GbpXdgRuntimeProvider *self = (GbpXdgRuntimeProvider *)object;
+ GPtrArray *ret;
+ GError *error = NULL;
+ guint i;
+
+ g_assert (GBP_IS_XDG_RUNTIME_PROVIDER (self));
+ g_assert (G_IS_TASK (result));
+
+ if (!(ret = g_task_propagate_pointer (G_TASK (result), &error)))
+ {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ for (i = 0; i < ret->len; i++)
+ {
+ IdeRuntime *runtime = g_ptr_array_index (ret, i);
+
+ ide_runtime_manager_add (self->manager, runtime);
+ }
+
+ self->runtimes = ret;
+}
+
+static void
+gbp_xdg_runtime_provider_load (IdeRuntimeProvider *provider,
+ IdeRuntimeManager *manager)
+{
+ GbpXdgRuntimeProvider *self = (GbpXdgRuntimeProvider *)provider;
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (GBP_IS_XDG_RUNTIME_PROVIDER (self));
+ g_assert (IDE_IS_RUNTIME_MANAGER (manager));
+
+ ide_set_weak_pointer (&self->manager, manager);
+
+ self->cancellable = g_cancellable_new ();
+
+ task = g_task_new (self, self->cancellable, gbp_xdg_runtime_provider_load_cb, NULL);
+ g_task_run_in_thread (task, gbp_xdg_runtime_provider_load_worker);
+}
+
+static void
+gbp_xdg_runtime_provider_unload (IdeRuntimeProvider *provider,
+ IdeRuntimeManager *manager)
+{
+ GbpXdgRuntimeProvider *self = (GbpXdgRuntimeProvider *)provider;
+
+ g_assert (GBP_IS_XDG_RUNTIME_PROVIDER (self));
+ g_assert (IDE_IS_RUNTIME_MANAGER (manager));
+
+ if (self->cancellable != NULL)
+ g_cancellable_cancel (self->cancellable);
+
+ ide_clear_weak_pointer (&self->manager);
+ g_clear_object (&self->cancellable);
+}
+
+static void
+gbp_xdg_runtime_provider_class_init (GbpXdgRuntimeProviderClass *klass)
+{
+}
+
+static void
+gbp_xdg_runtime_provider_init (GbpXdgRuntimeProvider *self)
+{
+}
+
+static void
+runtime_provider_iface_init (IdeRuntimeProviderInterface *iface)
+{
+ iface->load = gbp_xdg_runtime_provider_load;
+ iface->unload = gbp_xdg_runtime_provider_unload;
+}
diff --git a/plugins/xdg-app/gbp-xdg-runtime-provider.h b/plugins/xdg-app/gbp-xdg-runtime-provider.h
new file mode 100644
index 0000000..d1440e6
--- /dev/null
+++ b/plugins/xdg-app/gbp-xdg-runtime-provider.h
@@ -0,0 +1,32 @@
+/* gbp-xdg-runtime-provider.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GBP_XDG_RUNTIME_PROVIDER_H
+#define GBP_XDG_RUNTIME_PROVIDER_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_XDG_RUNTIME_PROVIDER (gbp_xdg_runtime_provider_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpXdgRuntimeProvider, gbp_xdg_runtime_provider, GBP, XDG_RUNTIME_PROVIDER, GObject)
+
+G_END_DECLS
+
+#endif /* GBP_XDG_RUNTIME_PROVIDER_H */
diff --git a/plugins/xdg-app/gbp-xdg-runtime.c b/plugins/xdg-app/gbp-xdg-runtime.c
new file mode 100644
index 0000000..3befc16
--- /dev/null
+++ b/plugins/xdg-app/gbp-xdg-runtime.c
@@ -0,0 +1,317 @@
+/* gb-xdg-runtime.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gbp-xdg-runtime.h"
+
+struct _GbpXdgRuntime
+{
+ IdeRuntime parent_instance;
+
+ gchar *sdk;
+ gchar *platform;
+ gchar *branch;
+};
+
+G_DEFINE_TYPE (GbpXdgRuntime, gbp_xdg_runtime, IDE_TYPE_RUNTIME)
+
+enum {
+ PROP_0,
+ PROP_BRANCH,
+ PROP_PLATFORM,
+ PROP_SDK,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static gchar *
+get_build_directory (GbpXdgRuntime *self)
+{
+ IdeContext *context;
+ IdeProject *project;
+
+ g_assert (GBP_IS_XDG_RUNTIME (self));
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ project = ide_context_get_project (context);
+
+ return g_build_filename (g_get_user_cache_dir (),
+ "gnome-builder",
+ "builds",
+ ide_project_get_name (project),
+ "xdg-app",
+ ide_runtime_get_id (IDE_RUNTIME (self)),
+ NULL);
+}
+
+static gboolean
+gbp_xdg_runtime_contains_program_in_path (IdeRuntime *runtime,
+ const gchar *program,
+ GCancellable *cancellable)
+{
+ g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+ g_autoptr(GSubprocess) subprocess = NULL;
+
+ g_assert (IDE_IS_RUNTIME (runtime));
+ g_assert (program != NULL);
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ launcher = ide_runtime_create_launcher (runtime, 0);
+
+ ide_subprocess_launcher_push_argv (launcher, "which");
+ ide_subprocess_launcher_push_argv (launcher, program);
+
+ subprocess = ide_subprocess_launcher_spawn_sync (launcher, cancellable, NULL);
+
+ return (subprocess != NULL) && g_subprocess_wait_check (subprocess, cancellable, NULL);
+}
+
+static void
+gbp_xdg_runtime_prebuild_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GbpXdgRuntime *self = source_object;
+ g_autofree gchar *build_path = NULL;
+ g_autoptr(GFile) build_dir = NULL;
+ g_autoptr(GSubprocessLauncher) launcher = NULL;
+ g_autoptr(GSubprocess) subprocess = NULL;
+ g_autoptr(GFile) parent = NULL;
+ GError *error = NULL;
+
+ g_assert (G_IS_TASK (task));
+ g_assert (GBP_IS_XDG_RUNTIME (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ build_path = get_build_directory (self);
+ build_dir = g_file_new_for_path (build_path);
+
+ if (g_file_query_exists (build_dir, cancellable))
+ {
+ g_task_return_boolean (task, TRUE);
+ return;
+ }
+
+ parent = g_file_get_parent (build_dir);
+
+ if (!g_file_query_exists (parent, cancellable))
+ {
+ if (!g_file_make_directory_with_parents (parent, cancellable, &error))
+ {
+ g_task_return_error (task, error);
+ return;
+ }
+ }
+
+ launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+ subprocess = g_subprocess_launcher_spawn (launcher, &error,
+ "xdg-app",
+ "build-init",
+ build_path,
+ /* XXX: Fake name, probably okay, but
+ * can be proper once we get IdeConfiguration
+ * in place.
+ */
+ "org.gnome.Builder.XdgApp.Build",
+ self->sdk,
+ self->platform,
+ self->branch,
+ NULL);
+
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_xdg_runtime_prebuild_async (IdeRuntime *runtime,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GbpXdgRuntime *self = (GbpXdgRuntime *)runtime;
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (GBP_IS_XDG_RUNTIME (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_run_in_thread (task, gbp_xdg_runtime_prebuild_worker);
+}
+
+static gboolean
+gbp_xdg_runtime_prebuild_finish (IdeRuntime *runtime,
+ GAsyncResult *result,
+ GError **error)
+{
+ GbpXdgRuntime *self = (GbpXdgRuntime *)runtime;
+
+ g_assert (GBP_IS_XDG_RUNTIME (self));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static IdeSubprocessLauncher *
+gbp_xdg_runtime_create_launcher (IdeRuntime *runtime,
+ GError **error)
+{
+ IdeSubprocessLauncher *ret;
+ GbpXdgRuntime *self = (GbpXdgRuntime *)runtime;
+
+ g_return_val_if_fail (GBP_IS_XDG_RUNTIME (self), NULL);
+
+ ret = IDE_RUNTIME_CLASS (gbp_xdg_runtime_parent_class)->create_launcher (runtime, error);
+
+ if (ret != NULL)
+ {
+ g_autofree gchar *build_path = get_build_directory (self);
+
+ ide_subprocess_launcher_push_argv (ret, "xdg-app");
+ ide_subprocess_launcher_push_argv (ret, "build");
+ ide_subprocess_launcher_push_argv (ret, build_path);
+ }
+
+ return ret;
+}
+
+static void
+gbp_xdg_runtime_prepare_configuration (IdeRuntime *runtime,
+ IdeConfiguration *configuration)
+{
+ g_assert (IDE_IS_RUNTIME (runtime));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ ide_configuration_set_prefix (configuration, "/app");
+}
+
+static void
+gbp_xdg_runtime_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbpXdgRuntime *self = GBP_XDG_RUNTIME(object);
+
+ switch (prop_id)
+ {
+ case PROP_BRANCH:
+ g_value_set_string (value, self->branch);
+ break;
+
+ case PROP_PLATFORM:
+ g_value_set_string (value, self->platform);
+ break;
+
+ case PROP_SDK:
+ g_value_set_string (value, self->sdk);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+gbp_xdg_runtime_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbpXdgRuntime *self = GBP_XDG_RUNTIME(object);
+
+ switch (prop_id)
+ {
+ case PROP_BRANCH:
+ self->branch = g_value_dup_string (value);
+ break;
+
+ case PROP_PLATFORM:
+ self->platform = g_value_dup_string (value);
+ break;
+
+ case PROP_SDK:
+ self->sdk = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+gbp_xdg_runtime_finalize (GObject *object)
+{
+ GbpXdgRuntime *self = (GbpXdgRuntime *)object;
+
+ g_clear_pointer (&self->sdk, g_free);
+ g_clear_pointer (&self->platform, g_free);
+ g_clear_pointer (&self->branch, g_free);
+
+ G_OBJECT_CLASS (gbp_xdg_runtime_parent_class)->finalize (object);
+}
+
+static void
+gbp_xdg_runtime_class_init (GbpXdgRuntimeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ IdeRuntimeClass *runtime_class = IDE_RUNTIME_CLASS (klass);
+
+ object_class->finalize = gbp_xdg_runtime_finalize;
+ object_class->get_property = gbp_xdg_runtime_get_property;
+ object_class->set_property = gbp_xdg_runtime_set_property;
+
+ runtime_class->prebuild_async = gbp_xdg_runtime_prebuild_async;
+ runtime_class->prebuild_finish = gbp_xdg_runtime_prebuild_finish;
+ runtime_class->create_launcher = gbp_xdg_runtime_create_launcher;
+ runtime_class->contains_program_in_path = gbp_xdg_runtime_contains_program_in_path;
+ runtime_class->prepare_configuration = gbp_xdg_runtime_prepare_configuration;
+
+ properties [PROP_BRANCH] =
+ g_param_spec_string ("branch",
+ "Branch",
+ "Branch",
+ "master",
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PLATFORM] =
+ g_param_spec_string ("platform",
+ "Platform",
+ "Platform",
+ "org.gnome.Platform",
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SDK] =
+ g_param_spec_string ("sdk",
+ "Sdk",
+ "Sdk",
+ "org.gnome.Sdk",
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gbp_xdg_runtime_init (GbpXdgRuntime *self)
+{
+}
diff --git a/plugins/xdg-app/gbp-xdg-runtime.h b/plugins/xdg-app/gbp-xdg-runtime.h
new file mode 100644
index 0000000..d2abc7a
--- /dev/null
+++ b/plugins/xdg-app/gbp-xdg-runtime.h
@@ -0,0 +1,32 @@
+/* gbp-xdg-runtime.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GBP_XDG_RUNTIME_H
+#define GBP_XDG_RUNTIME_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_XDG_RUNTIME (gbp_xdg_runtime_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpXdgRuntime, gbp_xdg_runtime, GBP, XDG_RUNTIME, IdeRuntime)
+
+G_END_DECLS
+
+#endif /* GBP_XDG_RUNTIME_H */
diff --git a/plugins/xdg-app/xdg-app.plugin b/plugins/xdg-app/xdg-app.plugin
new file mode 100644
index 0000000..b8b3d19
--- /dev/null
+++ b/plugins/xdg-app/xdg-app.plugin
@@ -0,0 +1,7 @@
+[Plugin]
+Module=xdg-app-plugin
+Name=Xdg-App
+Description=Provides support for building with Xdg-App
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2016 Christian Hergert
+Builtin=true
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]