/******************************************************************************* * Copyright (c) 2015 QNX Software Systems and others. * 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 *******************************************************************************/ package org.eclipse.cdt.arduino.core.internal.build; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.cdt.arduino.core.internal.Activator; import org.eclipse.cdt.arduino.core.internal.ArduinoPreferences; import org.eclipse.cdt.arduino.core.internal.HierarchicalProperties; import org.eclipse.cdt.arduino.core.internal.board.ArduinoBoard; import org.eclipse.cdt.arduino.core.internal.board.ArduinoLibrary; import org.eclipse.cdt.arduino.core.internal.board.ArduinoManager; import org.eclipse.cdt.arduino.core.internal.board.ArduinoPackage; import org.eclipse.cdt.arduino.core.internal.board.ArduinoPlatform; import org.eclipse.cdt.arduino.core.internal.board.ArduinoTool; import org.eclipse.cdt.arduino.core.internal.board.ToolDependency; import org.eclipse.cdt.arduino.core.internal.remote.ArduinoRemoteConnection; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.ConsoleOutputStream; import org.eclipse.cdt.core.ErrorParserManager; import org.eclipse.cdt.core.IConsoleParser; import org.eclipse.cdt.core.build.CBuildConfiguration; import org.eclipse.cdt.core.build.IToolChain; import org.eclipse.cdt.core.model.ICModelMarker; import org.eclipse.cdt.core.model.ISourceRoot; import org.eclipse.cdt.core.parser.ExtendedScannerInfo; import org.eclipse.cdt.core.parser.IScannerInfo; import org.eclipse.cdt.core.resources.IConsole; import org.eclipse.core.resources.IBuildConfiguration; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceProxy; import org.eclipse.core.resources.IResourceProxyVisitor; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.remote.core.IRemoteConnectionChangeListener; import org.eclipse.remote.core.IRemoteServicesManager; import org.eclipse.remote.core.RemoteConnectionChangeEvent; import freemarker.cache.TemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; public class ArduinoBuildConfiguration extends CBuildConfiguration implements TemplateLoader, IRemoteConnectionChangeListener { private static final ArduinoManager manager = Activator.getService(ArduinoManager.class); private static final boolean isWindows = Platform.getOS().equals(Platform.OS_WIN32); private final ArduinoRemoteConnection target; private final String launchMode; private ArduinoBoard defaultBoard; private Properties boardProperties; // for Makefile generation private Configuration templateConfig; /** * Default configuration. * * @param config */ ArduinoBuildConfiguration(IBuildConfiguration config, String name, String launchMode, ArduinoBoard defaultBoard, IToolChain toolChain) throws CoreException { super(config, ".default", toolChain); //$NON-NLS-1$ this.target = null; this.launchMode = launchMode; this.defaultBoard = defaultBoard; } ArduinoBuildConfiguration(IBuildConfiguration config, String name, String launchMode, ArduinoRemoteConnection target, IToolChain toolChain) throws CoreException { super(config, name, toolChain); this.target = target; this.launchMode = launchMode; IRemoteServicesManager remoteManager = Activator.getService(IRemoteServicesManager.class); remoteManager.addRemoteConnectionChangeListener(this); } @Override public synchronized void connectionChanged(RemoteConnectionChangeEvent event) { if (event.getConnection().equals(target.getRemoteConnection())) { boardProperties = null; } } @SuppressWarnings("unchecked") @Override public <T> T getAdapter(Class<T> adapter) { if (adapter.equals(ArduinoBuildConfiguration.class)) { return (T) this; } return super.getAdapter(adapter); } public String getLaunchMode() { return launchMode; } public ArduinoRemoteConnection getTarget() { return target; } public ArduinoBoard getBoard() throws CoreException { if (target != null) { return target.getBoard(); } else { return defaultBoard; } } private synchronized Properties getBoardProperties() throws CoreException { if (boardProperties == null) { ArduinoBoard board = getBoard(); ArduinoPlatform platform = board.getPlatform(); // IDE generated properties boardProperties = new Properties(); boardProperties.put("runtime.platform.path", platform.getInstallPath().toString()); //$NON-NLS-1$ boardProperties.put("runtime.ide.version", "10608"); //$NON-NLS-1$ //$NON-NLS-2$ boardProperties.put("software", "ARDUINO"); //$NON-NLS-1$ //$NON-NLS-2$ boardProperties.put("build.arch", platform.getArchitecture().toUpperCase()); //$NON-NLS-1$ boardProperties.put("build.path", "."); //$NON-NLS-1$ //$NON-NLS-2$ boardProperties.put("build.core.path", //$NON-NLS-1$ platform.getInstallPath().resolve("cores").resolve("{build.core}").toString()); //$NON-NLS-1$ //$NON-NLS-2$ boardProperties.put("build.system.path", platform.getInstallPath().resolve("system").toString()); //$NON-NLS-1$ //$NON-NLS-2$ boardProperties.put("build.variant.path", //$NON-NLS-1$ platform.getInstallPath().resolve("variants").resolve("{build.variant}").toString()); //$NON-NLS-1$ //$NON-NLS-2$ // Everyone seems to want to use arduino package tools ArduinoPackage arduinoPackage = manager.getPackage("arduino"); //$NON-NLS-1$ if (arduinoPackage != null) { for (ArduinoTool tool : arduinoPackage.getLatestTools()) { boardProperties.put("runtime.tools." + tool.getName() + ".path", tool.getInstallPath().toString()); //$NON-NLS-1$ //$NON-NLS-2$ } for (ArduinoTool tool : arduinoPackage.getTools()) { boardProperties.put("runtime.tools." + tool.getName() + '-' + tool.getVersion() + ".path", //$NON-NLS-1$ //$NON-NLS-2$ tool.getInstallPath().toString()); } } // Super Platform String core = board.getBoardProperties().getProperty("build.core"); //$NON-NLS-1$ if (core.contains(":")) { //$NON-NLS-1$ String[] segments = core.split(":"); //$NON-NLS-1$ if (segments.length == 2) { ArduinoPlatform superPlatform = manager.getInstalledPlatform(segments[0], platform.getArchitecture()); if (superPlatform != null) { boardProperties.putAll(superPlatform.getPlatformProperties()); } } } // Platform boardProperties.putAll(platform.getPlatformProperties()); // Tools for (ToolDependency toolDep : platform.getToolsDependencies()) { boardProperties.putAll(toolDep.getTool().getToolProperties()); } // Board boardProperties.putAll(board.getBoardProperties()); // Menus HierarchicalProperties menus = board.getMenus(); if (menus != null) { for (Entry<String, HierarchicalProperties> menuEntry : menus.getChildren().entrySet()) { String key = menuEntry.getKey(); String value = target.getMenuValue(key); if (value == null || value.isEmpty()) { Iterator<HierarchicalProperties> i = menuEntry.getValue().getChildren().values().iterator(); if (i.hasNext()) { HierarchicalProperties first = i.next(); value = first.getValue(); } } if (value != null && !value.isEmpty()) { boardProperties.putAll(board.getMenuProperties(key, value)); } } } } // always do this in case the project changes names boardProperties.put("build.project_name", getProject().getName()); //$NON-NLS-1$ return boardProperties; } public Map<String, Object> getBuildModel() throws CoreException { IProject project = getProject(); ArduinoBoard board = getBoard(); ArduinoPlatform platform = board.getPlatform(); Properties properties = new Properties(); Map<String, Object> buildModel = new HashMap<>(); buildModel.put("boardId", board.getId()); //$NON-NLS-1$ properties.put("object_file", "$@"); //$NON-NLS-1$ //$NON-NLS-2$ properties.put("source_file", "$<"); //$NON-NLS-1$ //$NON-NLS-2$ // The list of source files in the project final Path projectPath = new File(project.getLocationURI()).toPath(); final List<String> sourceFiles = new ArrayList<>(); for (ISourceRoot sourceRoot : CCorePlugin.getDefault().getCoreModel().create(project).getSourceRoots()) { sourceRoot.getResource().accept(new IResourceProxyVisitor() { @Override public boolean visit(IResourceProxy proxy) throws CoreException { if (proxy.getType() == IResource.FILE) { if (isSource(proxy.getName())) { Path sourcePath = new File(proxy.requestResource().getLocationURI()).toPath(); sourceFiles.add(pathString(projectPath.relativize(sourcePath))); } } return true; } }, 0); } buildModel.put("project_srcs", sourceFiles); //$NON-NLS-1$ // The list of library sources List<String> librarySources = new ArrayList<>(); for (ArduinoLibrary lib : manager.getLibraries(project)) { librarySources.addAll(lib.getSources()); } buildModel.put("libraries_srcs", librarySources); //$NON-NLS-1$ buildModel.put("libraries_path", pathString(ArduinoPreferences.getArduinoHome().resolve("libraries"))); //$NON-NLS-1$ //$NON-NLS-2$ // the recipes properties.putAll(getBoardProperties()); buildModel.put("build_path", properties.get("build.path")); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("project_name", project.getName()); //$NON-NLS-1$ String includes = null; for (Path include : getIncludePath(platform, properties)) { if (includes == null) { includes = "-I"; //$NON-NLS-1$ } else { includes += " -I"; //$NON-NLS-1$ } includes += '"' + pathString(include) + '"'; } for (ArduinoLibrary lib : manager.getLibraries(project)) { for (Path include : lib.getIncludePath()) { includes += " -I\"" + pathString(include) + '"'; //$NON-NLS-1$ } } // Magic recipes for platform builds with platform includes properties.put("includes", includes); //$NON-NLS-1$ buildModel.put("recipe_cpp_o_pattern_plat", resolveProperty("recipe.cpp.o.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("recipe_c_o_pattern_plat", resolveProperty("recipe.c.o.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("recipe_S_o_pattern_plat", resolveProperty("recipe.S.o.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ ArduinoPlatform corePlatform = platform; String core = properties.getProperty("build.core"); //$NON-NLS-1$ if (core.contains(":")) { //$NON-NLS-1$ String[] segments = core.split(":"); //$NON-NLS-1$ if (segments.length == 2) { corePlatform = manager.getInstalledPlatform(segments[0], platform.getArchitecture()); core = segments[1]; } } buildModel.put("platform_path", pathString(corePlatform.getInstallPath())); //$NON-NLS-1$ Path corePath = corePlatform.getInstallPath().resolve("cores").resolve(core); //$NON-NLS-1$ buildModel.put("platform_core_path", pathString(corePath)); //$NON-NLS-1$ List<String> coreSources = new ArrayList<>(); getSources(coreSources, corePath, true); buildModel.put("platform_core_srcs", coreSources); //$NON-NLS-1$ List<String> variantSources = new ArrayList<>(); String variant = properties.getProperty("build.variant"); //$NON-NLS-1$ if (variant != null) { ArduinoPlatform variantPlatform = platform; if (variant.contains(":")) { //$NON-NLS-1$ String[] segments = variant.split(":"); //$NON-NLS-1$ if (segments.length == 2) { variantPlatform = manager.getInstalledPlatform(segments[0], platform.getArchitecture()); variant = segments[1]; } } Path variantPath = variantPlatform.getInstallPath().resolve("variants").resolve(variant); //$NON-NLS-1$ buildModel.put("platform_variant_path", pathString(variantPath)); //$NON-NLS-1$ getSources(variantSources, variantPath, true); } buildModel.put("platform_variant_srcs", variantSources); //$NON-NLS-1$ properties.put("archive_file", "core.a"); //$NON-NLS-1$ //$NON-NLS-2$ properties.put("archive_file_path", "{build.path}/{archive_file}"); //$NON-NLS-1$ //$NON-NLS-2$ properties.put("object_files", "$(PROJECT_OBJS) $(LIBRARIES_OBJS)"); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("recipe_cpp_o_pattern", resolveProperty("recipe.cpp.o.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("recipe_c_o_pattern", resolveProperty("recipe.c.o.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("recipe_S_o_pattern", resolveProperty("recipe.S.o.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("recipe_ar_pattern", resolveProperty("recipe.ar.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("recipe_c_combine_pattern", resolveProperty("recipe.c.combine.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("recipe_objcopy_eep_pattern", resolveProperty("recipe.objcopy.eep.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("recipe_objcopy_hex_pattern", resolveProperty("recipe.objcopy.hex.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("recipe_objcopy_bin_pattern", resolveProperty("recipe.objcopy.bin.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ buildModel.put("recipe_size_pattern", resolveProperty("recipe.size.pattern", properties)); //$NON-NLS-1$ //$NON-NLS-2$ return buildModel; } private static void getSources(Collection<String> sources, Path dir, boolean recurse) { for (File file : dir.toFile().listFiles()) { if (file.isDirectory()) { if (recurse) { getSources(sources, file.toPath(), recurse); } } else { if (ArduinoBuildConfiguration.isSource(file.getName())) { sources.add(ArduinoBuildConfiguration.pathString(file.toPath())); } } } } public IFile generateMakeFile(IProgressMonitor monitor) throws CoreException { IFolder buildFolder = (IFolder) getBuildContainer(); if (!buildFolder.exists()) { buildFolder.create(true, true, monitor); } IFile makefile = buildFolder.getFile("Makefile"); //$NON-NLS-1$ Map<String, Object> buildModel = getBuildModel(); // Generate the Makefile try (StringWriter writer = new StringWriter()) { if (templateConfig == null) { templateConfig = new Configuration(Configuration.VERSION_2_3_22); templateConfig.setTemplateLoader(this); } Template template = templateConfig.getTemplate("templates/Makefile"); //$NON-NLS-1$ template.process(buildModel, writer); try (ByteArrayInputStream in = new ByteArrayInputStream( writer.getBuffer().toString().getBytes(StandardCharsets.UTF_8))) { createParent(makefile, monitor); if (makefile.exists()) { makefile.setContents(in, true, true, monitor); } else { makefile.create(in, true, monitor); } } } catch (IOException | TemplateException e) { throw new CoreException(new Status(IStatus.ERROR, Activator.getId(), "Error generating makefile", e)); } return makefile; } protected static void createParent(IResource child, IProgressMonitor monitor) throws CoreException { if (child == null) return; IContainer container = child.getParent(); if (container.exists()) { return; } IFolder parent = container.getAdapter(IFolder.class); createParent(parent, monitor); parent.create(true, true, monitor); } public static boolean isSource(String filename) { int i = filename.lastIndexOf('.'); String ext = filename.substring(i + 1); switch (ext) { case "cpp": //$NON-NLS-1$ case "c": //$NON-NLS-1$ case "S": //$NON-NLS-1$ return true; default: return false; } } private String resolvePropertyValue(String value, Properties dict) throws CoreException { String last; do { last = value; for (int i = value.indexOf('{'); i >= 0; i = value.indexOf('{', i)) { i++; if (value.charAt(i) == '{') { i++; continue; } int n = value.indexOf('}', i); if (n >= 0) { String p2 = value.substring(i, n); String r2 = dict.getProperty(p2); if (r2 != null) { value = value.replace('{' + p2 + '}', r2); } else { throw Activator.coreException(String.format("Undefined key %s", p2), null); } } i = n; } } while (!value.equals(last)); return value.replace("}}", "}").replace("{{", "{"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } private String resolveProperty(String property, Properties dict) throws CoreException { String value = dict.getProperty(property); return value != null ? resolvePropertyValue(value, dict) : null; } public String getMakeCommand() { return isWindows ? ArduinoPreferences.getArduinoHome().resolve("make").toString() : "make"; //$NON-NLS-1$ //$NON-NLS-2$ } public String[] getBuildCommand() throws CoreException { return new String[] { getMakeCommand() }; } public String[] getCleanCommand() throws CoreException { return new String[] { getMakeCommand(), "clean" }; //$NON-NLS-1$ } public String[] getSizeCommand() throws CoreException { // TODO this shouldn't be in the makefile // should be like the upload command return new String[] { getMakeCommand(), "size" }; //$NON-NLS-1$ } public String getCodeSizeRegex() throws CoreException { return getBoard().getPlatform().getPlatformProperties().getProperty("recipe.size.regex"); //$NON-NLS-1$ } public int getMaxCodeSize() throws CoreException { String sizeStr = getBoardProperties().getProperty("upload.maximum_size"); //$NON-NLS-1$ return sizeStr != null ? Integer.parseInt(sizeStr) : -1; } public String getDataSizeRegex() throws CoreException { return getBoard().getPlatform().getPlatformProperties().getProperty("recipe.size.regex.data"); //$NON-NLS-1$ } public int getMaxDataSize() throws CoreException { String sizeStr = getBoardProperties().getProperty("upload.maximum_data_size"); //$NON-NLS-1$ return sizeStr != null ? Integer.parseInt(sizeStr) : -1; } public String[] getUploadCommand(String serialPort) throws CoreException { Properties properties = new Properties(); properties.putAll(getBoardProperties()); String toolName = properties.getProperty("upload.tool"); //$NON-NLS-1$ ArduinoPlatform platform = getBoard().getPlatform(); if (toolName.contains(":")) { //$NON-NLS-1$ String[] segments = toolName.split(":"); //$NON-NLS-1$ if (segments.length == 2) { platform = manager.getInstalledPlatform(segments[0], platform.getArchitecture()); toolName = segments[1]; } } ArduinoTool uploadTool = platform.getTool(toolName); if (uploadTool != null) { properties.putAll(uploadTool.getToolProperties()); } properties.put("serial.port", serialPort); //$NON-NLS-1$ // Little bit of weirdness needed for the bossac tool if (serialPort.startsWith("/dev/")) { //$NON-NLS-1$ properties.put("serial.port.file", serialPort.substring(5)); //$NON-NLS-1$ } else { properties.put("serial.port.file", serialPort); //$NON-NLS-1$ } // to make up for some cheating in the platform.txt file properties.put("path", "{tools." + toolName + ".path}"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ properties.put("cmd.path", "{tools." + toolName + ".cmd.path}"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ properties.put("config.path", "{tools." + toolName + ".config.path}"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ // properties for the tool flattened HierarchicalProperties toolsProps = new HierarchicalProperties(platform.getPlatformProperties()) .getChild("tools"); //$NON-NLS-1$ if (toolsProps != null) { HierarchicalProperties toolProps = toolsProps.getChild(toolName); if (toolProps != null) { properties.putAll(toolProps.flatten()); } } String command; if (properties.get("upload.protocol") != null) { //$NON-NLS-1$ // TODO make this a preference properties.put("upload.verbose", properties.getProperty("upload.params.verbose", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ properties.put("upload.verify", properties.getProperty("upload.params.verify", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ command = resolveProperty("upload.pattern", properties); //$NON-NLS-1$ } else { // use the bootloader String programmer = target.getProgrammer(); if (programmer != null) { HierarchicalProperties programmers = getBoard().getPlatform().getProgrammers(); if (programmers != null) { HierarchicalProperties programmerProps = programmers.getChild(programmer); if (programmerProps != null) { properties.putAll(programmerProps.flatten()); } } } // TODO preference properties.put("program.verbose", properties.getProperty("program.params.verbose", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ properties.put("program.verify", properties.getProperty("program.params.verify", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ command = resolveProperty("program.pattern", properties); //$NON-NLS-1$ } if (command == null) { throw Activator.coreException("Upload command not specified", null); } if (isWindows) { return splitCommand(command); } else { return new String[] { "sh", "-c", command }; //$NON-NLS-1$ //$NON-NLS-2$ } } private Collection<Path> getIncludePath(ArduinoPlatform platform, Properties properties) throws CoreException { ArduinoPlatform corePlatform = platform; String core = properties.getProperty("build.core"); //$NON-NLS-1$ if (core.contains(":")) { //$NON-NLS-1$ String[] segments = core.split(":"); //$NON-NLS-1$ if (segments.length == 2) { corePlatform = manager.getInstalledPlatform(segments[0], platform.getArchitecture()); core = segments[1]; } } ArduinoPlatform variantPlatform = platform; String variant = properties.getProperty("build.variant"); //$NON-NLS-1$ if (variant != null) { if (variant.contains(":")) { //$NON-NLS-1$ String[] segments = variant.split(":"); //$NON-NLS-1$ if (segments.length == 2) { variantPlatform = manager.getInstalledPlatform(segments[0], platform.getArchitecture()); variant = segments[1]; } } } else { return Arrays.asList(corePlatform.getInstallPath().resolve("cores").resolve(core)); //$NON-NLS-1$ } return Arrays.asList(corePlatform.getInstallPath().resolve("cores").resolve(core), //$NON-NLS-1$ variantPlatform.getInstallPath().resolve("variants").resolve(variant)); //$NON-NLS-1$ } // Scanner Info Cache private String[] cachedIncludePath; private String cachedInfoCommand; private IScannerInfo cachedScannerInfo; @Override public IScannerInfo getScannerInformation(IResource resource) { try { IContentType contentType = CCorePlugin.getContentType(resource.getProject(), resource.getName()); String recipe; if (contentType != null && (contentType.getId() == CCorePlugin.CONTENT_TYPE_CSOURCE || contentType.getId() == CCorePlugin.CONTENT_TYPE_CSOURCE)) { recipe = "recipe.c.o.pattern"; //$NON-NLS-1$ } else { recipe = "recipe.cpp.o.pattern"; //$NON-NLS-1$ } ArduinoPlatform platform = getBoard().getPlatform(); Properties properties = new Properties(); properties.putAll(getBoardProperties()); // Overrides for scanner discovery properties.put("source_file", ""); //$NON-NLS-1$ //$NON-NLS-2$ properties.put("object_file", "-"); //$NON-NLS-1$ //$NON-NLS-2$ // the base scanner info does includes properties.put("includes", ""); //$NON-NLS-1$ //$NON-NLS-2$ String commandString = resolveProperty(recipe, properties); List<Path> includePath = new ArrayList<>(); includePath.addAll(getIncludePath(platform, properties)); Collection<ArduinoLibrary> libs = manager.getLibraries(getProject()); for (ArduinoLibrary lib : libs) { includePath.addAll(lib.getIncludePath()); } String[] includes = null; try { includes = includePath.stream().map(path -> { try { return resolvePropertyValue(path.toString(), properties); } catch (CoreException e) { throw new RuntimeException(e); } }).collect(Collectors.toList()).toArray(new String[includePath.size()]); } catch (RuntimeException e) { if (e.getCause() != null && e.getCause() instanceof CoreException) { throw (CoreException) e.getCause(); } else { throw e; } } // Use cache if we can if (cachedScannerInfo != null && cachedInfoCommand.equals(commandString) && cachedIncludePath.length == includes.length) { boolean matches = true; for (int i = 0; i < includes.length; ++i) { if (!includes[i].equals(cachedIncludePath[i])) { matches = false; break; } } if (matches) { return cachedScannerInfo; } } ExtendedScannerInfo baseInfo = new ExtendedScannerInfo(null, includes); String[] command = splitCommand(commandString); IScannerInfo info = getToolChain().getScannerInfo(getBuildConfiguration(), Paths.get(command[0]), Arrays.copyOfRange(command, 1, command.length), baseInfo, resource, getBuildDirectoryURI()); // cache the results cachedScannerInfo = info; cachedInfoCommand = commandString; cachedIncludePath = includes; return info; } catch (CoreException e) { Activator.log(e); return null; } } public static String pathString(Path path) { String str = path.toString(); if (isWindows) { str = str.replaceAll("\\\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$ } return str; } private String[] splitCommand(String command) { // TODO deal with quotes properly, for now just strip return command.replaceAll("\"", "").split("\\s+"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } @Override public IProject[] build(int kind, Map<String, String> args, IConsole console, IProgressMonitor monitor) throws CoreException { IProject project = getProject(); try { project.deleteMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); ConsoleOutputStream consoleOut = console.getOutputStream(); consoleOut.write(String.format("\nBuilding %s\n", project.getName())); generateMakeFile(monitor); try (ErrorParserManager epm = new ErrorParserManager(project, getBuildDirectoryURI(), this, getToolChain().getErrorParserIds())) { ProcessBuilder processBuilder = new ProcessBuilder().command(getBuildCommand()) .directory(getBuildDirectory().toFile()); setBuildEnvironment(processBuilder.environment()); Process process = processBuilder.start(); if (watchProcess(process, new IConsoleParser[] { epm }, console) == 0) { showSizes(console); } } getBuildContainer().refreshLocal(IResource.DEPTH_INFINITE, monitor); } catch (IOException e) { throw new CoreException(new Status(IStatus.ERROR, Activator.getId(), "Build error", e)); } // TODO if there are references we want to watch, return them here return new IProject[] { project }; } @Override public void clean(IConsole console, IProgressMonitor monitor) throws CoreException { try { IProject project = getProject(); project.deleteMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); ConsoleOutputStream consoleOut = console.getOutputStream(); consoleOut.write(String.format("\nCleaning %s\n", project.getName())); ProcessBuilder processBuilder = new ProcessBuilder().command(getCleanCommand()) .directory(getBuildDirectory().toFile()); setBuildEnvironment(processBuilder.environment()); Process process = processBuilder.start(); watchProcess(process, new IConsoleParser[0], console); getBuildContainer().refreshLocal(IResource.DEPTH_INFINITE, monitor); } catch (IOException e) { throw new CoreException(new Status(IStatus.ERROR, Activator.getId(), "Build error", e)); } } private void showSizes(IConsole console) throws CoreException { try { int codeSize = -1; int dataSize = -1; String codeSizeRegex = getCodeSizeRegex(); Pattern codeSizePattern = codeSizeRegex != null ? Pattern.compile(codeSizeRegex) : null; String dataSizeRegex = getDataSizeRegex(); Pattern dataSizePattern = dataSizeRegex != null ? Pattern.compile(dataSizeRegex) : null; if (codeSizePattern == null && dataSizePattern == null) { return; } int maxCodeSize = getMaxCodeSize(); int maxDataSize = getMaxDataSize(); ProcessBuilder processBuilder = new ProcessBuilder().command(getSizeCommand()) .directory(getBuildDirectory().toFile()).redirectErrorStream(true); setBuildEnvironment(processBuilder.environment()); Process process = processBuilder.start(); try (BufferedReader processOut = new BufferedReader(new InputStreamReader(process.getInputStream()))) { for (String line = processOut.readLine(); line != null; line = processOut.readLine()) { if (codeSizePattern != null) { Matcher matcher = codeSizePattern.matcher(line); if (matcher.matches()) { codeSize += Integer.parseInt(matcher.group(1)); } } if (dataSizePattern != null) { Matcher matcher = dataSizePattern.matcher(line); if (matcher.matches()) { dataSize += Integer.parseInt(matcher.group(1)); } } } } ConsoleOutputStream consoleOut = console.getOutputStream(); consoleOut.write("Program store usage: " + codeSize); if (maxCodeSize > 0) { consoleOut.write(" of maximum " + maxCodeSize); } consoleOut.write(" bytes\n"); if (maxDataSize >= 0) { consoleOut.write("Initial RAM usage: " + dataSize); if (maxCodeSize > 0) { consoleOut.write(" of maximum " + maxDataSize); } consoleOut.write(" bytes\n"); } } catch (IOException e) { throw new CoreException(new Status(IStatus.ERROR, Activator.getId(), "Checking sizes", e)); } } @Override public Object findTemplateSource(String name) throws IOException { return FileLocator.find(Activator.getPlugin().getBundle(), new org.eclipse.core.runtime.Path(name), null); } @Override public long getLastModified(Object source) { try { URL url = (URL) source; if (url.getProtocol().equals("file")) { //$NON-NLS-1$ File file = new File(url.toURI()); return file.lastModified(); } else { return 0; } } catch (URISyntaxException e) { return 0; } } @Override public Reader getReader(Object source, String encoding) throws IOException { URL url = (URL) source; return new InputStreamReader(url.openStream(), encoding); } @Override public void closeTemplateSource(Object arg0) throws IOException { // Nothing } }