blob: ec013937024909e43c8446a2fb3ae3d995ab3298 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016,2018 IAR Systems AB
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IAR Systems - initial API and implementation
* Red Hat Inc. - modified for use in Meson build
*******************************************************************************/
package org.eclipse.cdt.meson.ui.properties;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.CommandLauncherManager;
import org.eclipse.cdt.core.ICommandLauncher;
import org.eclipse.cdt.core.build.CBuildConfiguration;
import org.eclipse.cdt.core.build.ICBuildCommandLauncher;
import org.eclipse.cdt.core.build.ICBuildConfiguration;
import org.eclipse.cdt.core.resources.IConsole;
import org.eclipse.cdt.meson.core.IMesonConstants;
import org.eclipse.cdt.meson.ui.Activator;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.dialogs.PropertyPage;
/**
* Property page for Meson projects. For unconfigured projects, we use the meson command and parse
* the output of the --help option. Otherwise, we use the meson configure command to find current
* options and what may be changed via a meson configure call.
*
* We assume that the build directory is in project/build/configname, which is where
* the CMake project wizard puts it. We also assume that "cmake-gui" is in the user's
* PATH.
*/
public class MesonPropertyPage extends PropertyPage {
private IProject project;
private List<IMesonPropertyPageControl> componentList = new ArrayList<>();
private boolean configured;
private CBuildConfiguration buildConfig;
private Text envText;
private Text projText;
@Override
protected Control createContents(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
composite.setLayout(new GridLayout(1, true));
project = (IProject) getElement();
String configName;
try {
buildConfig = ((CBuildConfiguration)project.getActiveBuildConfig().getAdapter(ICBuildConfiguration.class));
configName = ((CBuildConfiguration)project.getActiveBuildConfig().getAdapter(ICBuildConfiguration.class)).getName();
IPath sourceDir = project.getLocation();
String buildDir = project.getLocation().append("build").append(configName).toOSString(); //$NON-NLS-1$
IPath buildPath = new Path(buildDir).append("build.ninja"); //$NON-NLS-1$
configured = buildPath.toFile().exists();
if (configured) {
ICommandLauncher launcher = CommandLauncherManager.getInstance().getCommandLauncher(project.getActiveBuildConfig().getAdapter(ICBuildConfiguration.class));
launcher.setProject(project);
if (launcher instanceof ICBuildCommandLauncher) {
((ICBuildCommandLauncher)launcher).setBuildConfiguration(buildConfig);
}
Process p = launcher.execute(new Path("meson"), new String[] { "configure", buildDir}, //$NON-NLS-1$ //$NON-NLS-2$
new String[0], sourceDir, new NullProgressMonitor());
if (p != null) {
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
ByteArrayOutputStream stderr = new ByteArrayOutputStream();
int rc = -1;
try {
if (launcher.waitAndRead(stdout, stderr, new NullProgressMonitor()) == ICommandLauncher.OK) {
p.waitFor();
}
rc = p.exitValue();
} catch (InterruptedException e) {
// ignore for now
}
if (rc == 0) {
componentList = parseConfigureOutput(stdout, composite);
}
}
} else {
ICommandLauncher launcher = CommandLauncherManager.getInstance().getCommandLauncher(project.getActiveBuildConfig().getAdapter(ICBuildConfiguration.class));
launcher.setProject(project);
if (launcher instanceof ICBuildCommandLauncher) {
((ICBuildCommandLauncher)launcher).setBuildConfiguration(buildConfig);
}
Process p = launcher.execute(new Path("meson"), new String[] { "-h"}, //$NON-NLS-1$ //$NON-NLS-2$
new String[0], sourceDir, new NullProgressMonitor());
if (p == null) {
return null;
}
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
ByteArrayOutputStream stderr = new ByteArrayOutputStream();
int rc = -1;
try {
if (launcher.waitAndRead(stdout, stderr, new NullProgressMonitor()) == ICommandLauncher.OK) {
p.waitFor();
}
rc = p.exitValue();
} catch (InterruptedException e) {
// ignore for now
}
if (rc == 0) {
Map<String, String> argMap = new HashMap<>();
String mesonArgs = buildConfig.getProperty(IMesonConstants.MESON_ARGUMENTS);
if (mesonArgs != null) {
String[] argStrings = mesonArgs.split("--"); //$NON-NLS-1$
for (String argString : argStrings) {
if (!argString.isEmpty()) {
String[] s = argString.split("="); //$NON-NLS-1$
if (s.length == 2) {
argMap.put(s[0], s[1].trim());
} else {
argMap.put(argString.trim(), "true"); //$NON-NLS-1$
}
}
}
}
Group group = new Group(composite, SWT.BORDER);
GridLayout layout = new GridLayout(2, true);
layout.marginLeft = 10;
layout.marginRight = 10;
group.setLayout(layout);
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setText(Messages.MesonPropertyPage_env_group);
Label envLabel = new Label(group, SWT.NONE);
envLabel.setText(Messages.MesonPropertyPage_env_label);
GridData data = new GridData(GridData.FILL, GridData.FILL, true, false);
data.grabExcessHorizontalSpace = true;
data.horizontalSpan = 1;
envLabel.setLayoutData(data);
String mesonEnv = buildConfig.getProperty(IMesonConstants.MESON_ENV);
envText = new Text(group, SWT.BORDER);
if (mesonEnv != null) {
envText.setText(mesonEnv);
}
envText.setToolTipText(Messages.MesonPropertyPage_env_tooltip);
data = new GridData(GridData.FILL, GridData.FILL, true, false);
data.grabExcessHorizontalSpace = true;
data.horizontalSpan = 1;
envText.setLayoutData(data);
group = new Group(composite, SWT.BORDER);
layout = new GridLayout(2, true);
layout.marginLeft = 10;
layout.marginRight = 10;
group.setLayout(layout);
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setText(Messages.MesonPropertyPage_project_group);
Label projLabel = new Label(group, SWT.NONE);
projLabel.setText(Messages.MesonPropertyPage_project_label);
data = new GridData(GridData.FILL, GridData.FILL, true, false);
data.grabExcessHorizontalSpace = true;
data.horizontalSpan = 1;
projLabel.setLayoutData(data);
String mesonProjOptions = buildConfig.getProperty(IMesonConstants.MESON_PROJECT_OPTIONS);
projText = new Text(group, SWT.BORDER);
if (mesonProjOptions != null) {
projText.setText(mesonProjOptions);
}
projText.setToolTipText(Messages.MesonPropertyPage_project_tooltip);
data = new GridData(GridData.FILL, GridData.FILL, true, false);
data.grabExcessHorizontalSpace = true;
data.horizontalSpan = 1;
projText.setLayoutData(data);
// default buildtype based on active build configuration
// user can always override and we will use override from then on
String defaultBuildType = "release"; //$NON-NLS-1$
if (configName.contains("debug")) { //$NON-NLS-1$
defaultBuildType = "debug"; //$NON-NLS-1$
}
if (argMap.get("buildtype") == null) { //$NON-NLS-1$
argMap.put("buildtype", defaultBuildType); //$NON-NLS-1$
}
componentList = parseHelpOutput(stdout, composite, argMap, defaultBuildType);
}
}
} catch (CoreException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}
return composite;
}
public void update() {
setErrorMessage(null);
for (IMesonPropertyPageControl control : componentList) {
if (!control.isValid()) {
setValid(false);
setErrorMessage(control.getErrorMessage());
}
}
}
public enum ParseState {
INIT, GROUP, OPTION, OPTION_WITH_VALUES, ARGS
};
@Override
public boolean performOk() {
List<String> args = new ArrayList<>();
if (configured) {
args.add("configure"); //$NON-NLS-1$
for (IMesonPropertyPageControl control : componentList) {
if (control.isValueChanged()) {
args.add(control.getConfiguredString()); //$NON-NLS-1$ //$NON-NLS-2$
}
}
if (args.size() == 2) {
return true;
}
try {
String configName = ((CBuildConfiguration)project.getActiveBuildConfig().getAdapter(ICBuildConfiguration.class)).getName();
IPath sourceDir = project.getLocation();
String buildDir = project.getLocation().append("build").append(configName).toOSString(); //$NON-NLS-1$
ICommandLauncher launcher = CommandLauncherManager.getInstance().getCommandLauncher(project.getActiveBuildConfig().getAdapter(ICBuildConfiguration.class));
launcher.setProject(project);
if (launcher instanceof ICBuildCommandLauncher) {
((ICBuildCommandLauncher)launcher).setBuildConfiguration(buildConfig);
}
args.add(buildDir);
Process p = launcher.execute(new Path("meson"), args.toArray(new String[0]), new String[0], sourceDir, new NullProgressMonitor()); //$NON-NLS-1$ //$NON-NLS-2$
int rc = -1;
IConsole console = CCorePlugin.getDefault().getConsole();
console.start(project);
try (OutputStream stdout = console.getOutputStream()) {
OutputStream stderr = stdout;
StringBuilder buf = new StringBuilder();
for (String arg : args) {
buf.append(arg);
buf.append(" "); //$NON-NLS-1$
}
buf.append(System.lineSeparator());
stdout.write(buf.toString().getBytes());
stdout.flush();
try {
if (launcher.waitAndRead(stdout, stderr, new NullProgressMonitor()) == ICommandLauncher.OK) {
p.waitFor();
}
rc = p.exitValue();
stdout.write(NLS.bind(Messages.MesonPropertyPage_terminated_rc, rc).getBytes());
stdout.flush();
if (rc != 0) {
Display.getDefault().syncExec(() -> {
MessageDialog.openError(getShell(), null, Messages.MesonPropertyPage_configure_failed);
});
}
} catch (InterruptedException e) {
// ignore for now
}
} catch (IOException e2) {
Activator.log(e2);
return false;
}
} catch (CoreException e3) {
// TODO Auto-generated catch block
Activator.log(e3);
return false;
}
} else {
StringBuilder mesonargs = new StringBuilder();
for (IMesonPropertyPageControl control : componentList) {
if (!control.getUnconfiguredString().isEmpty()) {
mesonargs.append(control.getUnconfiguredString());
mesonargs.append(" "); //$NON-NLS-1$
}
}
buildConfig.setProperty(IMesonConstants.MESON_ARGUMENTS, mesonargs.toString());
buildConfig.setProperty(IMesonConstants.MESON_ENV, envText.getText().trim());
buildConfig.setProperty(IMesonConstants.MESON_PROJECT_OPTIONS, projText.getText().trim());
}
return true;
}
/**
* Parse output of meson help call to determine options to show to user
* @param stdout - ByteArrayOutputStream containing output of command
* @param composite - Composite to add Controls to
* @return - list of Controls
*/
List<IMesonPropertyPageControl> parseHelpOutput(ByteArrayOutputStream stdout, Composite composite, Map<String, String> argMap, String defaultBuildType) {
List<IMesonPropertyPageControl> controls = new ArrayList<>();
Group group = new Group(composite, SWT.BORDER);
GridLayout layout = new GridLayout(2, true);
layout.marginLeft = 10;
layout.marginRight = 10;
group.setLayout(layout);
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setText(Messages.MesonPropertyPage_options_group);
try {
String output = stdout.toString(StandardCharsets.UTF_8.name()).replaceAll("\\r?\\n\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$
String[] lines = output.split("--"); //$NON-NLS-1$
Pattern optionPattern = Pattern.compile("(([a-z-]+)\\s+(([A-Z_][A-Z_]+))?\\s*(\\{.*?\\})?([^\\[\\]]*))");
Pattern descPattern1 = Pattern.compile("([^\\.]+).*");
Pattern descPattern = Pattern.compile("([^\\(]*)(\\(default\\:\\s+([^\\)]+)\\).*)");
for (String line : lines) {
Matcher optionMatcher = optionPattern.matcher(line);
if (optionMatcher.matches() && !optionMatcher.group(2).equals("help")) {
if (optionMatcher.group(3) != null) {
String defaultValue = argMap.get(optionMatcher.group(2));
String description = optionMatcher.group(6);
Matcher m = descPattern1.matcher(optionMatcher.group(6));
if (m.matches()) {
description = m.group(1).trim();
}
IMesonPropertyPageControl control = new MesonPropertyText(group, optionMatcher.group(2), defaultValue, description);
controls.add(control);
} else if (optionMatcher.group(5) != null) {
String defaultValue = argMap.get(optionMatcher.group(2));
Matcher m = descPattern.matcher(optionMatcher.group(6));
if (m.matches()) {
String valueString = optionMatcher.group(5).replaceAll("\\{", ""); //$NON-NLS-1$ //$NON-NLS-2$
valueString = valueString.replaceAll("\\}", ""); //$NON-NLS-1$ //$NON-NLS-2$
String[] values = valueString.split(","); //$NON-NLS-1$
if (defaultValue == null) {
defaultValue = m.group(3).trim();
}
IMesonPropertyPageControl control = new MesonPropertyCombo(group, optionMatcher.group(2), values, defaultValue, m.group(1).trim());
controls.add(control);
}
} else {
boolean defaultValue = false;
if (argMap.containsKey(optionMatcher.group(2))) {
defaultValue = Boolean.parseBoolean(argMap.get(optionMatcher.group(2)));
}
IMesonPropertyPageControl control = new MesonPropertySpecialCheckbox(group, optionMatcher.group(2), defaultValue, optionMatcher.group(6));
controls.add(control);
}
}
}
} catch (UnsupportedEncodingException e) {
return controls;
}
return controls;
}
/**
* Parse output of meson configure call to determine options to show to user
* @param stdout - ByteArrayOutputStream containing output of command
* @param composite - Composite to add Controls to
* @return - list of Controls
*/
List<IMesonPropertyPageControl> parseConfigureOutput(ByteArrayOutputStream stdout, Composite composite) {
List<IMesonPropertyPageControl> controls = new ArrayList<>();
try {
String[] lines = stdout.toString(StandardCharsets.UTF_8.name()).split("\\r?\\n"); //$NON-NLS-1$
ParseState state = ParseState.INIT;
Pattern optionPattern = Pattern.compile(Messages.MesonPropertyPage_option_pattern);
Pattern optionWithValuesPattern = Pattern.compile(Messages.MesonPropertyPage_option_with_values_pattern);
Pattern optionLine = Pattern.compile("(\\w+)\\s+([\\w,\\-,/]+)\\s+(.*)$"); //$NON-NLS-1$
Pattern optionWithValuesLine = Pattern.compile("(\\w+)\\s+([\\w,\\-,/]+)\\s+\\[([\\w,\\-,/]+)((,\\s+[\\w,\\-]+)*)\\]\\s+(.*)$");
Pattern compilerOrLinkerArgs = Pattern.compile(Messages.MesonPropertyPage_compiler_or_link_args);
Pattern argLine = Pattern.compile("(\\w+)\\s+\\[([^\\]]*)\\]"); //$NON-NLS-1$
Pattern groupPattern = Pattern.compile("(([^:]*)):"); //$NON-NLS-1$
String groupName = ""; //$NON-NLS-1$
Composite parent = composite;
for (String line : lines) {
line = line.trim();
boolean unprocessed = true;
while (unprocessed) {
unprocessed = false;
switch (state) {
case INIT:
Matcher argMatcher = compilerOrLinkerArgs.matcher(line);
if (argMatcher.matches()) {
state = ParseState.ARGS;
Group group = new Group(composite, SWT.BORDER);
group.setLayout(new GridLayout(2, true));
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setText(argMatcher.group(1));
parent = group;
} else {
Matcher groupMatcher = groupPattern.matcher(line);
if (groupMatcher.matches()) {
groupName = groupMatcher.group(1);
state = ParseState.GROUP;
}
parent = composite;
}
break;
case GROUP:
Matcher m = optionPattern.matcher(line);
if (m.matches()) {
state = ParseState.OPTION;
if (parent == composite) {
Group group = new Group(composite, SWT.BORDER);
group.setLayout(new GridLayout(2, true));
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setText(groupName);
parent = group;
}
break;
}
m = optionWithValuesPattern.matcher(line);
if (m.matches()) {
state = ParseState.OPTION_WITH_VALUES;
if (parent == composite) {
Group group = new Group(composite, SWT.BORDER);
group.setLayout(new GridLayout(2, true));
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setText(groupName);
parent = group;
}
break;
}
if (line.contains(":")) { //$NON-NLS-1$
state = ParseState.INIT;
unprocessed = true;
parent = composite;
}
break;
case ARGS:
Matcher m2 = argLine.matcher(line);
if (m2.matches()) {
String argName = m2.group(1);
String argValue = m2.group(2);
argValue = argValue.replaceAll("',", ""); //$NON-NLS-1$ //$NON-NLS-2$
argValue = argValue.replaceAll("'", ""); //$NON-NLS-1$ //$NON-NLS-2$
String argDescription = Messages.MesonPropertyPage_arg_description;
IMesonPropertyPageControl argControl = new MesonPropertyText(parent, argName, argValue, argDescription);
controls.add(argControl);
}
state = ParseState.INIT;
parent = composite;
break;
case OPTION:
Matcher m3 = optionLine.matcher(line);
if (line.startsWith("----")) { //$NON-NLS-1$
break;
}
if (line.isEmpty()) {
state = ParseState.INIT;
parent = composite;
break;
}
if (m3.matches()) {
String name = m3.group(1);
String value = m3.group(2);
String description = m3.group(3);
boolean isInteger = false;
try {
Integer.parseInt(value);
isInteger = true;
} catch (NumberFormatException e) {
// do nothing
}
if (isInteger) {
IMesonPropertyPageControl control = new MesonPropertyInteger(parent, this, name, value, description);
controls.add(control);
} else if (Messages.MesonPropertyPage_true.equals(value) ||
Messages.MesonPropertyPage_false.equals(value)) {
IMesonPropertyPageControl control = new MesonPropertyCheckbox(parent, name, Boolean.getBoolean(value), description);
controls.add(control);
} else {
IMesonPropertyPageControl control = new MesonPropertyText(parent, name, value, description);
controls.add(control);
}
} else {
if (line.contains(":")) { //$NON-NLS-1$
state = ParseState.INIT;
parent = composite;
unprocessed = true;
} else {
state = ParseState.GROUP;
unprocessed = true;
}
}
break;
case OPTION_WITH_VALUES:
Matcher m4 = optionWithValuesLine.matcher(line);
if (line.startsWith("----")) { //$NON-NLS-1$
break;
}
if (line.isEmpty()) {
state = ParseState.INIT;
parent = composite;
break;
}
if (m4.matches()) {
String name = m4.group(1);
String value = m4.group(2);
String possibleValue = m4.group(3);
String extraValues = m4.group(4);
String description = m4.group(6);
String[] values = new String[] {possibleValue};
if (!extraValues.isEmpty()) {
values = extraValues.split(",\\s+");
values[0] = possibleValue;
}
IMesonPropertyPageControl control = new MesonPropertyCombo(parent, name, values, value, description);
controls.add(control);
} else {
if (line.contains(":")) { //$NON-NLS-1$
state = ParseState.INIT;
parent = composite;
unprocessed = true;
} else {
state = ParseState.GROUP;
unprocessed = true;
}
}
break;
}
}
}
} catch (UnsupportedEncodingException e) {
return controls;
}
return controls;
}
}