| /******************************************************************************* |
| * 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; |
| } |
| |
| } |