diff --git a/.gitignore b/.gitignore index 6e6a7592170..0c7b200cf38 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ hardware/arduino/bootloaders/caterina_LUFA/Caterina.elf hardware/arduino/bootloaders/caterina_LUFA/Caterina.eep hardware/arduino/bootloaders/caterina_LUFA/.dep/ build/*.zip +build/*.tar.bz2 build/windows/work/ build/windows/*.zip build/windows/*.tgz diff --git a/app/src/cc/arduino/ConsoleOutputStream.java b/app/src/cc/arduino/ConsoleOutputStream.java index b4460398495..d1f021a27b4 100644 --- a/app/src/cc/arduino/ConsoleOutputStream.java +++ b/app/src/cc/arduino/ConsoleOutputStream.java @@ -116,9 +116,9 @@ private void clearBuffer() { if (document != null) { SwingUtilities.invokeLater(() -> { try { - String lineWithoutSlashR = line.replace("\r\n", "\n").replace("\r", "\n"); + String lineWithoutCR = line.replace("\r\n", "\n").replace("\r", "\n"); int offset = document.getLength(); - document.insertString(offset, lineWithoutSlashR, attributes); + document.insertString(offset, lineWithoutCR, attributes); } catch (BadLocationException ble) { //ignore } diff --git a/app/src/cc/arduino/packages/formatter/AStyle.java b/app/src/cc/arduino/packages/formatter/AStyle.java index 1e1ceb2c991..702f34ec86b 100644 --- a/app/src/cc/arduino/packages/formatter/AStyle.java +++ b/app/src/cc/arduino/packages/formatter/AStyle.java @@ -1,5 +1,3 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - /* * This file is part of Arduino. * diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 75d4de1c7d2..e69647701bf 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -750,6 +750,10 @@ public void actionPerformed(ActionEvent e) { item = newJMenuItemAlt(tr("Export compiled Binary"), 'S'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { + if (new ShouldSaveReadOnly().test(sketch) && !handleSave(true)) { + System.out.println(tr("Export canceled, changes must first be saved.")); + return; + } handleRun(false, new ShouldSaveReadOnly(), Editor.this.presentAndSaveHandler, Editor.this.runAndSaveHandler); } }); diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 140b9a0a742..f8c8adf3828 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -23,11 +23,14 @@ package processing.app; +import cc.arduino.Compiler; +import cc.arduino.CompilerProgressListener; +import cc.arduino.UploaderUtils; +import cc.arduino.files.DeleteFilesOnShutdown; import cc.arduino.packages.Uploader; -import processing.app.debug.Compiler; -import processing.app.debug.Compiler.ProgressListener; import processing.app.debug.RunnerException; import processing.app.forms.PasswordAuthorizationDialog; +import processing.app.helpers.FileUtils; import processing.app.helpers.OSUtils; import processing.app.helpers.PreferencesMapException; import processing.app.packages.LibraryList; @@ -37,10 +40,14 @@ import java.awt.*; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static processing.app.I18n.tr; @@ -60,7 +67,7 @@ public class Sketch { private int currentIndex; private final SketchData data; - + /** * path is location of the main .pde file, because this is also * simplest to use when opening the file from the finder/explorer. @@ -1093,7 +1100,7 @@ public void prepare() throws IOException { * @return null if compilation failed, main class name if not * @throws RunnerException */ - public String build(boolean verbose, boolean save) throws RunnerException, PreferencesMapException { + public String build(boolean verbose, boolean save) throws RunnerException, PreferencesMapException, IOException { return build(tempBuildFolder.getAbsolutePath(), verbose, save); } @@ -1106,15 +1113,32 @@ public String build(boolean verbose, boolean save) throws RunnerException, Prefe * * @return null if compilation failed, main class name if not */ - private String build(String buildPath, boolean verbose, boolean save) throws RunnerException, PreferencesMapException { + private String build(String buildPath, boolean verbose, boolean save) throws RunnerException, PreferencesMapException, IOException { // run the preprocessor editor.status.progressUpdate(20); ensureExistence(); - - ProgressListener pl = editor.status::progressUpdate; - - return Compiler.build(data, buildPath, tempBuildFolder, pl, verbose, save); + + CompilerProgressListener progressListener = editor.status::progressUpdate; + + String pathToSketch = data.getMainFilePath(); + if (isModified()) { + pathToSketch = saveSketchInTempFolder(); + } + + return new Compiler(pathToSketch, data, buildPath).build(progressListener, save); + } + + private String saveSketchInTempFolder() throws IOException { + File tempFolder = FileUtils.createTempFolder(); + DeleteFilesOnShutdown.add(tempFolder); + FileUtils.copy(getFolder(), tempFolder); + + for (SketchCode sc : Stream.of(data.getCodes()).filter(SketchCode::isModified).collect(Collectors.toList())) { + Files.write(Paths.get(tempFolder.getAbsolutePath(), sc.getFileName()), sc.getProgram().getBytes()); + } + + return Paths.get(tempFolder.getAbsolutePath(), data.getPrimaryFile().getName()).toString(); } protected boolean exportApplet(boolean usingProgrammer) throws Exception { @@ -1153,7 +1177,7 @@ private boolean exportApplet(String appletPath, boolean usingProgrammer) private boolean upload(String buildPath, String suggestedClassName, boolean usingProgrammer) throws Exception { - Uploader uploader = Compiler.getUploaderByPreferences(false); + Uploader uploader = new UploaderUtils().getUploaderByPreferences(false); boolean success = false; do { @@ -1172,7 +1196,7 @@ private boolean upload(String buildPath, String suggestedClassName, boolean usin List warningsAccumulator = new LinkedList<>(); try { - success = Compiler.upload(data, uploader, buildPath, suggestedClassName, usingProgrammer, false, warningsAccumulator); + success = new UploaderUtils().upload(data, uploader, buildPath, suggestedClassName, usingProgrammer, false, warningsAccumulator); } finally { if (uploader.requiresAuthorization() && !success) { PreferencesData.remove(uploader.getAuthorizationKey()); diff --git a/app/src/processing/app/debug/MessageStream.java b/app/src/processing/app/debug/MessageStream.java deleted file mode 100644 index 9ea7caeb900..00000000000 --- a/app/src/processing/app/debug/MessageStream.java +++ /dev/null @@ -1,62 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2004-08 Ben Fry and Casey Reas - Copyright (c) 2001-04 Massachusetts Institute of Technology - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -package processing.app.debug; - -import java.io.*; - - -/** - * OutputStream to handle stdout/stderr messages. - *

- * This is used by Editor, System.err is set to - * new PrintStream(new MessageStream()). - * It's also used by Compiler. - */ -class MessageStream extends OutputStream { - - MessageConsumer messageConsumer; - - public MessageStream(MessageConsumer messageConsumer) { - this.messageConsumer = messageConsumer; - } - - public void close() { } - - public void flush() { } - - public void write(byte b[]) { - // this never seems to get called - System.out.println("leech1: " + new String(b)); - } - - public void write(byte b[], int offset, int length) { - //System.out.println("leech2: " + new String(b)); - this.messageConsumer.message(new String(b, offset, length)); - } - - public void write(int b) { - // this never seems to get called - System.out.println("leech3: '" + ((char)b) + "'"); - } -} diff --git a/app/test/cc/arduino/i18n/ExternalProcessOutputParserTest.java b/app/test/cc/arduino/i18n/ExternalProcessOutputParserTest.java new file mode 100644 index 00000000000..5bd56ac7142 --- /dev/null +++ b/app/test/cc/arduino/i18n/ExternalProcessOutputParserTest.java @@ -0,0 +1,95 @@ +/* + * This file is part of Arduino. + * + * Copyright 2015 Arduino LLC (http://www.arduino.cc/) + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ + +package cc.arduino.i18n; + +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class ExternalProcessOutputParserTest { + + @Test + public void testParser1() throws Exception { + Map output = new ExternalProcessOutputParser().parse("===WARNING: Category '{0}' in library {1} is not valid. Setting to '{2}' ||| [ Wire Uncategorized]"); + + assertEquals("WARNING: Category '{0}' in library {1} is not valid. Setting to '{2}'", output.get("msg")); + Object[] args = (Object[]) output.get("args"); + assertEquals(3, args.length); + assertEquals("", args[0]); + assertEquals("Wire", args[1]); + assertEquals("Uncategorized", args[2]); + } + + @Test + public void testParser2() throws Exception { + Map output = new ExternalProcessOutputParser().parse("===Using previously compiled file: {0} ||| [%2Ftmp%2Farduino-sketch-456612873D8321DA02916066CB8B2FE6%2Flibraries%2FBridge%2FBridge.cpp.o]"); + + assertEquals("Using previously compiled file: {0}", output.get("msg")); + Object[] args = (Object[]) output.get("args"); + assertEquals(1, args.length); + assertEquals("/tmp/arduino-sketch-456612873D8321DA02916066CB8B2FE6/libraries/Bridge/Bridge.cpp.o", args[0]); + } + + @Test + public void testParser3() throws Exception { + Map output = new ExternalProcessOutputParser().parse("===Using library {0} at version {1} in folder: {2} {3} {4} ||| [Stepper 1.1.1 %2Fhome%2Ffederico%2Fmateriale%2Fworks_Arduino%2FArduino%2Fbuild%2Flinux%2Fwork%2Flibraries%2FStepper ]"); + + assertEquals("Using library {0} at version {1} in folder: {2} {3} {4}", output.get("msg")); + Object[] args = (Object[]) output.get("args"); + assertEquals(5, args.length); + assertEquals("Stepper", args[0]); + assertEquals("1.1.1", args[1]); + assertEquals("/home/federico/materiale/works_Arduino/Arduino/build/linux/work/libraries/Stepper", args[2]); + assertEquals("", args[3]); + assertEquals("", args[4]); + } + + @Test + public void testParser4() throws Exception { + Map output = new ExternalProcessOutputParser().parse("==={0} ||| []"); + + assertEquals("{0}", output.get("msg")); + Object[] args = (Object[]) output.get("args"); + assertEquals(0, args.length); + } + + @Test + public void testParser5() throws Exception { + Map output = new ExternalProcessOutputParser().parse("==={0} ||| [ ]"); + + assertEquals("{0}", output.get("msg")); + Object[] args = (Object[]) output.get("args"); + assertEquals(1, args.length); + assertEquals("", args[0]); + } + +} diff --git a/app/test/cc/arduino/i18n/I18NTest.java b/app/test/cc/arduino/i18n/I18NTest.java new file mode 100644 index 00000000000..1edbb5d134a --- /dev/null +++ b/app/test/cc/arduino/i18n/I18NTest.java @@ -0,0 +1,56 @@ +/* + * This file is part of Arduino. + * + * Copyright 2015 Arduino LLC (http://www.arduino.cc/) + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ + +package cc.arduino.i18n; + +import org.junit.Test; +import processing.app.AbstractWithPreferencesTest; +import processing.app.I18n; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class I18NTest extends AbstractWithPreferencesTest { + + @Test + public void testMessageFormat() throws Exception { + Object[] args = new Object[]{"a", "b", "c"}; + String actual = I18n.format("WARNING: Category '{0}' in library {1} is not valid. Setting to '{2}'", args); + assertEquals("WARNING: Category 'a' in library b is not valid. Setting to 'c'", actual); + } + + @Test + public void testMessageFormatFromExternalProcess() throws Exception { + Map output = new ExternalProcessOutputParser().parse("===WARNING: Category '{0}' in library {1} is not valid. Setting to '{2}' ||| [ Wire Uncategorized]"); + + String actual = I18n.format((String) output.get("msg"), (Object[])output.get("args")); + assertEquals("WARNING: Category '' in library Wire is not valid. Setting to 'Uncategorized'", actual); + } +} diff --git a/app/test/processing/app/debug/CompilerTest.java b/app/test/processing/app/debug/OldCompilerTest.java similarity index 94% rename from app/test/processing/app/debug/CompilerTest.java rename to app/test/processing/app/debug/OldCompilerTest.java index 9662df24ace..b7cb5619b1a 100755 --- a/app/test/processing/app/debug/CompilerTest.java +++ b/app/test/processing/app/debug/OldCompilerTest.java @@ -30,13 +30,13 @@ package processing.app.debug; import static org.junit.Assert.assertEquals; -import static processing.app.debug.Compiler.unescapeDepFile; +import static processing.app.debug.OldCompiler.unescapeDepFile; import org.junit.Test; import processing.app.AbstractWithPreferencesTest; -public class CompilerTest extends AbstractWithPreferencesTest { +public class OldCompilerTest extends AbstractWithPreferencesTest { @Test public void makeDepUnescapeTest() throws Exception { diff --git a/arduino-core/src/cc/arduino/Compiler.java b/arduino-core/src/cc/arduino/Compiler.java new file mode 100644 index 00000000000..c627bf1bdc9 --- /dev/null +++ b/arduino-core/src/cc/arduino/Compiler.java @@ -0,0 +1,584 @@ +/* + * This file is part of Arduino. + * + * Copyright 2015 Arduino LLC (http://www.arduino.cc/) + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ + +package cc.arduino; + +import cc.arduino.i18n.I18NAwareMessageConsumer; +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.PumpStreamHandler; +import processing.app.*; +import processing.app.debug.*; +import processing.app.helpers.PreferencesMap; +import processing.app.helpers.PreferencesMapException; +import processing.app.helpers.StringReplacer; +import processing.app.legacy.PApplet; +import processing.app.tools.DoubleQuotedArgumentsOnWindowsCommandLine; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static processing.app.I18n.tr; + +public class Compiler implements MessageConsumer { + + //used by transifex integration + static { + tr("'arch' folder is no longer supported! See http://goo.gl/gfFJzU for more information"); + tr("Board {0} (platform {1}, package {2}) is unknown"); + tr("Bootloader file specified but missing: {0}"); + tr("Build options changed, rebuilding all"); + tr("Unable to find {0} in {1}"); + tr("Invalid quoting: no closing [{0}] char found."); + tr("(legacy)"); + tr("Multiple libraries were found for \"{0}\""); + tr(" Not used: {0}"); + tr(" Used: {0}"); + tr("Library can't use both 'src' and 'utility' folders."); + tr("WARNING: library {0} claims to run on {1} architecture(s) and may be incompatible with your current board which runs on {2} architecture(s)."); + tr("Looking for recipes like {0}*{1}"); + tr("Board {0}:{1}:{2} doesn''t define a ''build.board'' preference. Auto-set to: {3}"); + tr("Third-party platform.txt does not define compiler.path. Please report this to the third-party hardware maintainer."); + tr("Selected board depends on '{0}' core (not installed)."); + tr("{0} must be a folder"); + tr("{0}: Unknown package"); + tr("{0} pattern is missing"); + tr("Platform {0} (package {1}) is unknown"); + tr("Progress {0}"); + tr("Missing '{0}' from library in {1}"); + tr("Running: {0}"); + tr("Running recipe: {0}"); + tr("Setting build path to {0}"); + tr("Unhandled type {0} in context {1} key"); + tr("Unknown sketch file extension: {0}"); + tr("Using library {0} at version {1} in folder: {2} {3}"); + tr("Using library {0} in folder: {1} {2}"); + tr("Using previously compiled file: {0}"); + tr("WARNING: Category '{0}' in library {1} is not valid. Setting to '{2}'"); + tr("Warning: platform.txt from core '{0}' misses property '{1}', using default value '{2}'. Consider upgrading this core."); + tr("Warning: platform.txt from core '{0}' contains deprecated {1}, automatically converted to {2}. Consider upgrading this core."); + tr("WARNING: Spurious {0} folder in '{1}' library"); + } + + enum BuilderAction { + COMPILE("-compile"), DUMP_PREFS("-dump-prefs"); + + private final String value; + + BuilderAction(String value) { + this.value = value; + } + } + + private final String pathToSketch; + private final SketchData sketch; + private final String buildPath; + private boolean verbose; + private RunnerException exception; + + public Compiler(SketchData data, String buildPath) { + this(data.getMainFilePath(), data, buildPath); + } + + public Compiler(String pathToSketch, SketchData sketch, String buildPath) { + this.pathToSketch = pathToSketch; + this.sketch = sketch; + this.buildPath = buildPath; + this.verbose = PreferencesData.getBoolean("build.verbose"); + } + + public String build(CompilerProgressListener progListener, boolean exportHex) throws RunnerException, PreferencesMapException, IOException { + TargetBoard board = BaseNoGui.getTargetBoard(); + if (board == null) { + throw new RunnerException("Board is not selected"); + } + + TargetPlatform platform = board.getContainerPlatform(); + TargetPackage aPackage = platform.getContainerPackage(); + + PreferencesMap prefs = loadPreferences(board, platform, aPackage); + + MessageConsumerOutputStream out = new MessageConsumerOutputStream(new ProgressAwareMessageConsumer(new I18NAwareMessageConsumer(System.out), progListener), "\n"); + MessageConsumerOutputStream err = new MessageConsumerOutputStream(new I18NAwareMessageConsumer(System.err, Compiler.this), "\n"); + + callArduinoBuilder(board, platform, aPackage, BuilderAction.COMPILE, new PumpStreamHandler(out, err)); + + out.flush(); + err.flush(); + + if (exportHex) { + runActions("hooks.savehex.presavehex", prefs); + + saveHex(prefs); + + runActions("hooks.savehex.postsavehex", prefs); + } + + size(prefs); + + return sketch.getName() + ".ino"; + } + + private PreferencesMap loadPreferences(TargetBoard board, TargetPlatform platform, TargetPackage aPackage) throws RunnerException, IOException { + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + MessageConsumerOutputStream err = new MessageConsumerOutputStream(new I18NAwareMessageConsumer(new PrintStream(stderr), Compiler.this), "\n"); + try { + callArduinoBuilder(board, platform, aPackage, BuilderAction.DUMP_PREFS, new PumpStreamHandler(stdout, err)); + } catch (RunnerException e) { + System.err.println(new String(stderr.toByteArray())); + throw e; + } + PreferencesMap prefs = new PreferencesMap(); + prefs.load(new ByteArrayInputStream(stdout.toByteArray())); + return prefs; + } + + private void callArduinoBuilder(TargetBoard board, TargetPlatform platform, TargetPackage aPackage, BuilderAction action, PumpStreamHandler streamHandler) throws RunnerException { + File executable = BaseNoGui.getContentFile("arduino-builder"); + CommandLine commandLine = new CommandLine(executable); + commandLine.addArgument(action.value, false); + commandLine.addArgument("-logger=machine", false); + + Stream.of(BaseNoGui.getHardwarePath(), new File(BaseNoGui.getSettingsFolder(), "packages").getAbsolutePath(), BaseNoGui.getSketchbookHardwareFolder().getAbsolutePath()) + .forEach(p -> { + if (Files.exists(Paths.get(p))) { + commandLine.addArgument("-hardware=\"" + p + "\"", false); + } + }); + + Stream.of(BaseNoGui.getContentFile("tools-builder").getAbsolutePath(), Paths.get(BaseNoGui.getHardwarePath(), "tools", "avr").toAbsolutePath().toString(), new File(BaseNoGui.getSettingsFolder(), "packages").getAbsolutePath()) + .forEach(p -> { + if (Files.exists(Paths.get(p))) { + commandLine.addArgument("-tools=\"" + p + "\"", false); + } + }); + + commandLine.addArgument("-libraries=\"" + BaseNoGui.getSketchbookLibrariesFolder().getAbsolutePath() + "\"", false); + commandLine.addArgument("-libraries=\"" + BaseNoGui.getContentFile("libraries").getAbsolutePath() + "\"", false); + + String fqbn = Stream.of(aPackage.getId(), platform.getId(), board.getId(), boardOptions(board)).filter(s -> !s.isEmpty()).collect(Collectors.joining(":")); + commandLine.addArgument("-fqbn=" + fqbn, false); + + commandLine.addArgument("-ide-version=" + BaseNoGui.REVISION, false); + commandLine.addArgument("-build-path=\"" + buildPath + "\"", false); + commandLine.addArgument("-warnings=" + PreferencesData.get("compiler.warning_level"), false); + + PreferencesData.getMap() + .subTree("build_properties_custom") + .entrySet() + .stream() + .forEach(kv -> commandLine.addArgument("-prefs=\"" + kv.getKey() + "=" + kv.getValue() + "\"", false)); + + commandLine.addArgument("-prefs=build.warn_data_percentage=" + PreferencesData.get("build.warn_data_percentage")); + + //commandLine.addArgument("-debug-level=10", false); + + if (verbose) { + commandLine.addArgument("-verbose", false); + } + + commandLine.addArgument("\"" + pathToSketch + "\"", false); + + if (verbose) { + System.out.println(commandLine); + } + + DefaultExecutor executor = new DefaultExecutor(); + executor.setStreamHandler(streamHandler); + + int result; + executor.setExitValues(null); + try { + result = executor.execute(commandLine); + } catch (IOException e) { + RunnerException re = new RunnerException(e.getMessage()); + re.hideStackTrace(); + throw re; + } + executor.setExitValues(new int[0]); + + if (exception != null) + throw exception; + + if (result > 1) { + System.err.println(I18n.format(tr("{0} returned {1}"), executable.getName(), result)); + } + + if (result != 0) { + RunnerException re = new RunnerException(tr("Error compiling.")); + re.hideStackTrace(); + throw re; + } + } + + protected void size(PreferencesMap prefs) throws RunnerException { + String maxTextSizeString = prefs.get("upload.maximum_size"); + String maxDataSizeString = prefs.get("upload.maximum_data_size"); + + if (maxTextSizeString == null) { + return; + } + + long maxTextSize = Integer.parseInt(maxTextSizeString); + long maxDataSize = -1; + + if (maxDataSizeString != null) { + maxDataSize = Integer.parseInt(maxDataSizeString); + } + + Sizer sizer = new Sizer(prefs); + long[] sizes; + try { + sizes = sizer.computeSize(); + } catch (RunnerException e) { + System.err.println(I18n.format(tr("Couldn't determine program size: {0}"), e.getMessage())); + return; + } + + long textSize = sizes[0]; + long dataSize = sizes[1]; + System.out.println(); + System.out.println(I18n.format(tr("Sketch uses {0} bytes ({2}%%) of program storage space. Maximum is {1} bytes."), textSize, maxTextSize, textSize * 100 / maxTextSize)); + if (dataSize >= 0) { + if (maxDataSize > 0) { + System.out.println(I18n.format(tr("Global variables use {0} bytes ({2}%%) of dynamic memory, leaving {3} bytes for local variables. Maximum is {1} bytes."), dataSize, maxDataSize, dataSize * 100 / maxDataSize, maxDataSize - dataSize)); + } else { + System.out.println(I18n.format(tr("Global variables use {0} bytes of dynamic memory."), dataSize)); + } + } + + if (textSize > maxTextSize) { + throw new RunnerException(tr("Sketch too big; see http://www.arduino.cc/en/Guide/Troubleshooting#size for tips on reducing it.")); + } + + if (maxDataSize > 0 && dataSize > maxDataSize) { + throw new RunnerException(tr("Not enough memory; see http://www.arduino.cc/en/Guide/Troubleshooting#size for tips on reducing your footprint.")); + } + + int warnDataPercentage = Integer.parseInt(prefs.get("build.warn_data_percentage")); + if (maxDataSize > 0 && dataSize > maxDataSize * warnDataPercentage / 100) { + System.err.println(tr("Low memory available, stability problems may occur.")); + } + } + + void saveHex(PreferencesMap prefs) throws RunnerException { + List compiledSketches = new ArrayList<>(prefs.subTree("recipe.output.tmp_file", 1).values()); + List copyOfCompiledSketches = new ArrayList<>(prefs.subTree("recipe.output.save_file", 1).values()); + + if (isExportCompiledSketchSupported(compiledSketches, copyOfCompiledSketches, prefs)) { + System.err.println(tr("Warning: This core does not support exporting sketches. Please consider upgrading it or contacting its author")); + return; + } + + PreferencesMap dict = new PreferencesMap(prefs); + dict.put("ide_version", "" + BaseNoGui.REVISION); + PreferencesMap withBootloaderDict = new PreferencesMap(dict); + dict.put("build.project_name", dict.get("build.project_name") + ".with_bootloader"); + + if (!compiledSketches.isEmpty()) { + for (int i = 0; i < compiledSketches.size(); i++) { + saveHex(compiledSketches.get(i), copyOfCompiledSketches.get(i), dict); + saveHex(compiledSketches.get(i), copyOfCompiledSketches.get(i), withBootloaderDict); + } + } else { + try { + saveHex(prefs.getOrExcept("recipe.output.tmp_file"), prefs.getOrExcept("recipe.output.save_file"), dict); + saveHex(prefs.getOrExcept("recipe.output.tmp_file"), prefs.getOrExcept("recipe.output.save_file"), withBootloaderDict); + } catch (PreferencesMapException e) { + throw new RunnerException(e); + } + } + } + + private void saveHex(String compiledSketch, String copyOfCompiledSketch, PreferencesMap prefs) throws RunnerException { + try { + compiledSketch = StringReplacer.replaceFromMapping(compiledSketch, prefs); + copyOfCompiledSketch = StringReplacer.replaceFromMapping(copyOfCompiledSketch, prefs); + + Path compiledSketchPath; + Path compiledSketchPathInSubfolder = Paths.get(prefs.get("build.path"), "sketch", compiledSketch); + Path compiledSketchPathInBuildPath = Paths.get(prefs.get("build.path"), compiledSketch); + if (Files.exists(compiledSketchPathInSubfolder)) { + compiledSketchPath = compiledSketchPathInSubfolder; + } else { + compiledSketchPath = compiledSketchPathInBuildPath; + } + + Path copyOfCompiledSketchFilePath = Paths.get(this.sketch.getFolder().getAbsolutePath(), copyOfCompiledSketch); + + Files.copy(compiledSketchPath, copyOfCompiledSketchFilePath, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new RunnerException(e); + } + } + + private boolean isExportCompiledSketchSupported(List compiledSketches, List copyOfCompiledSketches, PreferencesMap prefs) { + return (compiledSketches.isEmpty() || copyOfCompiledSketches.isEmpty() || copyOfCompiledSketches.size() < compiledSketches.size()) && (!prefs.containsKey("recipe.output.tmp_file") || !prefs.containsKey("recipe.output.save_file")); + } + + void runActions(String recipeClass, PreferencesMap prefs) throws RunnerException, PreferencesMapException { + List patterns = new ArrayList<>(); + for (String key : prefs.keySet()) { + if (key.startsWith("recipe." + recipeClass) && key.endsWith(".pattern")) + patterns.add(key); + } + Collections.sort(patterns); + for (String recipe : patterns) { + runRecipe(recipe, prefs); + } + } + + void runRecipe(String recipe, PreferencesMap prefs) throws RunnerException, PreferencesMapException { + PreferencesMap dict = new PreferencesMap(prefs); + dict.put("ide_version", "" + BaseNoGui.REVISION); + dict.put("sketch_path", sketch.getFolder().getAbsolutePath()); + + String[] cmdArray; + String cmd = prefs.getOrExcept(recipe); + try { + cmdArray = StringReplacer.formatAndSplit(cmd, dict, true); + } catch (Exception e) { + throw new RunnerException(e); + } + exec(cmdArray); + } + + private void exec(String[] command) throws RunnerException { + // eliminate any empty array entries + List stringList = new ArrayList(); + for (String string : command) { + string = string.trim(); + if (string.length() != 0) + stringList.add(string); + } + command = stringList.toArray(new String[stringList.size()]); + if (command.length == 0) + return; + + if (verbose) { + for (String c : command) + System.out.print(c + " "); + System.out.println(); + } + + DefaultExecutor executor = new DefaultExecutor(); + executor.setStreamHandler(new PumpStreamHandler() { + + @Override + protected Thread createPump(InputStream is, OutputStream os, boolean closeWhenExhausted) { + final Thread result = new Thread(new MyStreamPumper(is, Compiler.this)); + result.setDaemon(true); + return result; + + } + }); + + CommandLine commandLine = new DoubleQuotedArgumentsOnWindowsCommandLine(command[0]); + for (int i = 1; i < command.length; i++) { + commandLine.addArgument(command[i], false); + } + + int result; + executor.setExitValues(null); + try { + result = executor.execute(commandLine); + } catch (IOException e) { + RunnerException re = new RunnerException(e.getMessage()); + re.hideStackTrace(); + throw re; + } + executor.setExitValues(new int[0]); + + // an error was queued up by message(), barf this back to compile(), + // which will barf it back to Editor. if you're having trouble + // discerning the imagery, consider how cows regurgitate their food + // to digest it, and the fact that they have five stomaches. + // + //System.out.println("throwing up " + exception); + if (exception != null) + throw exception; + + if (result > 1) { + // a failure in the tool (e.g. unable to locate a sub-executable) + System.err + .println(I18n.format(tr("{0} returned {1}"), command[0], result)); + } + + if (result != 0) { + RunnerException re = new RunnerException(tr("Error compiling.")); + re.hideStackTrace(); + throw re; + } + } + + private String boardOptions(TargetBoard board) { + return board.getMenuIds().stream() + .filter(board::hasMenu) + .filter(menuId -> { + String entry = PreferencesData.get("custom_" + menuId); + return entry != null && entry.startsWith(board.getId()); + }) + .map(menuId -> { + String entry = PreferencesData.get("custom_" + menuId); + String selectionId = entry.substring(board.getId().length() + 1); + return menuId + "=" + selectionId; + }) + .collect(Collectors.joining(",")); + } + + /** + * Part of the MessageConsumer interface, this is called + * whenever a piece (usually a line) of error message is spewed + * out from the compiler. The errors are parsed for their contents + * and line number, which is then reported back to Editor. + */ + public void message(String s) { + int i; + + if (!verbose) { + while ((i = s.indexOf(buildPath + File.separator)) != -1) { + s = s.substring(0, i) + s.substring(i + (buildPath + File.separator).length()); + } + } + + String errorFormat = "(.+\\.\\w+):(\\d+)(:\\d+)*:\\s*error:\\s*(.*)\\s*"; + String[] pieces = PApplet.match(s, errorFormat); + + if (pieces != null) { + String error = pieces[pieces.length - 1], msg = ""; + + if (error.trim().equals("SPI.h: No such file or directory")) { + error = tr("Please import the SPI library from the Sketch > Import Library menu."); + msg = tr("\nAs of Arduino 0019, the Ethernet library depends on the SPI library." + + "\nYou appear to be using it or another library that depends on the SPI library.\n\n"); + } + + if (error.trim().equals("'BYTE' was not declared in this scope")) { + error = tr("The 'BYTE' keyword is no longer supported."); + msg = tr("\nAs of Arduino 1.0, the 'BYTE' keyword is no longer supported." + + "\nPlease use Serial.write() instead.\n\n"); + } + + if (error.trim().equals("no matching function for call to 'Server::Server(int)'")) { + error = tr("The Server class has been renamed EthernetServer."); + msg = tr("\nAs of Arduino 1.0, the Server class in the Ethernet library " + + "has been renamed to EthernetServer.\n\n"); + } + + if (error.trim().equals("no matching function for call to 'Client::Client(byte [4], int)'")) { + error = tr("The Client class has been renamed EthernetClient."); + msg = tr("\nAs of Arduino 1.0, the Client class in the Ethernet library " + + "has been renamed to EthernetClient.\n\n"); + } + + if (error.trim().equals("'Udp' was not declared in this scope")) { + error = tr("The Udp class has been renamed EthernetUdp."); + msg = tr("\nAs of Arduino 1.0, the Udp class in the Ethernet library " + + "has been renamed to EthernetUdp.\n\n"); + } + + if (error.trim().equals("'class TwoWire' has no member named 'send'")) { + error = tr("Wire.send() has been renamed Wire.write()."); + msg = tr("\nAs of Arduino 1.0, the Wire.send() function was renamed " + + "to Wire.write() for consistency with other libraries.\n\n"); + } + + if (error.trim().equals("'class TwoWire' has no member named 'receive'")) { + error = tr("Wire.receive() has been renamed Wire.read()."); + msg = tr("\nAs of Arduino 1.0, the Wire.receive() function was renamed " + + "to Wire.read() for consistency with other libraries.\n\n"); + } + + if (error.trim().equals("'Mouse' was not declared in this scope")) { + error = tr("'Mouse' only supported on the Arduino Leonardo"); + //msg = _("\nThe 'Mouse' class is only supported on the Arduino Leonardo.\n\n"); + } + + if (error.trim().equals("'Keyboard' was not declared in this scope")) { + error = tr("'Keyboard' only supported on the Arduino Leonardo"); + //msg = _("\nThe 'Keyboard' class is only supported on the Arduino Leonardo.\n\n"); + } + + RunnerException exception = placeException(error, pieces[1], PApplet.parseInt(pieces[2]) - 1); + + // replace full file path with the name of the sketch tab (unless we're + // in verbose mode, in which case don't modify the compiler output) + if (exception != null && !verbose) { + SketchCode code = sketch.getCode(exception.getCodeIndex()); + String fileName = (code.isExtension("ino") || code.isExtension("pde")) ? code.getPrettyName() : code.getFileName(); + int lineNum = exception.getCodeLine() + 1; + s = fileName + ":" + lineNum + ": error: " + error + msg; + } + + if (exception != null) { + if (this.exception == null || this.exception.getMessage().equals(exception.getMessage())) { + this.exception = exception; + this.exception.hideStackTrace(); + } + } + } + + if (s.contains("undefined reference to `SPIClass::begin()'") && + s.contains("libraries/Robot_Control")) { + String error = tr("Please import the SPI library from the Sketch > Import Library menu."); + exception = new RunnerException(error); + } + + if (s.contains("undefined reference to `Wire'") && + s.contains("libraries/Robot_Control")) { + String error = tr("Please import the Wire library from the Sketch > Import Library menu."); + exception = new RunnerException(error); + } + + System.err.println(s); + } + + public RunnerException placeException(String message, + String dotJavaFilename, + int dotJavaLine) { + // Placing errors is simple, because we inserted #line directives + // into the preprocessed source. The compiler gives us correct + // the file name and line number. :-) + for (SketchCode code : sketch.getCodes()) { + if (dotJavaFilename.equals(code.getFileName())) { + return new RunnerException(message, sketch.indexOfCode(code), dotJavaLine); + } + } + return null; + } +} diff --git a/arduino-core/src/cc/arduino/CompilerProgressListener.java b/arduino-core/src/cc/arduino/CompilerProgressListener.java new file mode 100644 index 00000000000..1831005cd39 --- /dev/null +++ b/arduino-core/src/cc/arduino/CompilerProgressListener.java @@ -0,0 +1,36 @@ +/* + * This file is part of Arduino. + * + * Copyright 2015 Arduino LLC (http://www.arduino.cc/) + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ + +package cc.arduino; + +public interface CompilerProgressListener { + + void progress(int value); + +} diff --git a/arduino-core/src/cc/arduino/CompilerUtils.java b/arduino-core/src/cc/arduino/CompilerUtils.java new file mode 100644 index 00000000000..95dd47f4c9d --- /dev/null +++ b/arduino-core/src/cc/arduino/CompilerUtils.java @@ -0,0 +1,62 @@ +/* + * This file is part of Arduino. + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + * + * Copyright 2015 Arduino LLC (http://www.arduino.cc/) + */ + +package cc.arduino; + +import processing.app.helpers.PreferencesMap; +import processing.app.helpers.PreferencesMapException; +import processing.app.helpers.StringReplacer; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static processing.app.I18n.tr; + +public class CompilerUtils { + + public File findCompiledSketch(PreferencesMap prefs) throws PreferencesMapException { + List paths = Arrays.asList( + "{build.path}/sketch/{build.project_name}.with_bootloader.hex", + "{build.path}/sketch/{build.project_name}.hex", + "{build.path}/{build.project_name}.with_bootloader.hex", + "{build.path}/{build.project_name}.hex", + "{build.path}/sketch/{build.project_name}.bin", + "{build.path}/{build.project_name}.bin" + ); + Optional sketch = paths.stream(). + map(path -> StringReplacer.replaceFromMapping(path, prefs)). + map(File::new). + filter(File::exists). + findFirst(); + return sketch.orElseThrow(() -> new IllegalStateException(tr("No compiled sketch found"))); + } + +} diff --git a/arduino-core/src/cc/arduino/MessageConsumerOutputStream.java b/arduino-core/src/cc/arduino/MessageConsumerOutputStream.java new file mode 100644 index 00000000000..ea4aedf0526 --- /dev/null +++ b/arduino-core/src/cc/arduino/MessageConsumerOutputStream.java @@ -0,0 +1,46 @@ +package cc.arduino; + +import processing.app.debug.MessageConsumer; + +import java.io.ByteArrayOutputStream; + +public class MessageConsumerOutputStream extends ByteArrayOutputStream { + + private final MessageConsumer consumer; + private final String lineSeparator; + + public MessageConsumerOutputStream(MessageConsumer consumer) { + this(consumer, System.getProperty("line.separator")); + } + + public MessageConsumerOutputStream(MessageConsumer consumer, String lineSeparator) { + this.consumer = consumer; + this.lineSeparator = lineSeparator; + } + + @Override + public void write(byte[] b, int off, int len) { + super.write(b, off, len); + + flush(); + } + + @Override + public void flush() { + String asString = toString(); + if (!asString.contains(lineSeparator)) { + return; + } + + while (asString.contains(lineSeparator)) { + String line = asString.substring(0, asString.indexOf(lineSeparator)); + reset(); + byte[] bytes = asString.substring(asString.indexOf(lineSeparator) + lineSeparator.length()).getBytes(); + super.write(bytes, 0, bytes.length); + asString = toString(); + + consumer.message(line); + } + + } +} diff --git a/arduino-core/src/cc/arduino/MyStreamPumper.java b/arduino-core/src/cc/arduino/MyStreamPumper.java index 003fad07c86..890f05ffb0c 100644 --- a/arduino-core/src/cc/arduino/MyStreamPumper.java +++ b/arduino-core/src/cc/arduino/MyStreamPumper.java @@ -73,7 +73,7 @@ public void run() { try { String line; while ((line = reader.readLine()) != null) { - messageConsumer.message(line + "\n"); + messageConsumer.message(line); } } catch (Exception e) { // nothing to do - happens quite often with watchdog diff --git a/arduino-core/src/cc/arduino/ProgressAwareMessageConsumer.java b/arduino-core/src/cc/arduino/ProgressAwareMessageConsumer.java new file mode 100644 index 00000000000..d22515b55f3 --- /dev/null +++ b/arduino-core/src/cc/arduino/ProgressAwareMessageConsumer.java @@ -0,0 +1,62 @@ +/* + * This file is part of Arduino. + * + * Copyright 2015 Arduino LLC (http://www.arduino.cc/) + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ + +package cc.arduino; + +import cc.arduino.i18n.ExternalProcessOutputParser; +import processing.app.debug.MessageConsumer; + +import java.util.Map; + +public class ProgressAwareMessageConsumer implements MessageConsumer { + + private final MessageConsumer parent; + private final CompilerProgressListener progressListener; + private final ExternalProcessOutputParser parser; + + public ProgressAwareMessageConsumer(MessageConsumer parent, CompilerProgressListener progressListener) { + this.parent = parent; + this.progressListener = progressListener; + this.parser = new ExternalProcessOutputParser(); + } + + @Override + public void message(String s) { + if (s.startsWith("===Progress")) { + Map parsedMessage = parser.parse(s); + Object[] args = (Object[]) parsedMessage.get("args"); + progressListener.progress(Double.valueOf(args[0].toString()).intValue()); + return; + } + + if (parent != null) { + parent.message(s); + } + } +} diff --git a/arduino-core/src/cc/arduino/UploaderUtils.java b/arduino-core/src/cc/arduino/UploaderUtils.java new file mode 100644 index 00000000000..b243c30d216 --- /dev/null +++ b/arduino-core/src/cc/arduino/UploaderUtils.java @@ -0,0 +1,97 @@ +/* + * This file is part of Arduino. + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + * + * Copyright 2015 Arduino LLC (http://www.arduino.cc/) + */ + +package cc.arduino; + +import cc.arduino.packages.BoardPort; +import cc.arduino.packages.Uploader; +import cc.arduino.packages.UploaderFactory; +import processing.app.BaseNoGui; +import processing.app.PreferencesData; +import processing.app.SketchData; +import processing.app.debug.TargetPlatform; + +import java.util.LinkedList; +import java.util.List; + +import static processing.app.I18n.tr; + +public class UploaderUtils { + + public Uploader getUploaderByPreferences(boolean noUploadPort) { + TargetPlatform target = BaseNoGui.getTargetPlatform(); + String board = PreferencesData.get("board"); + + BoardPort boardPort = null; + if (!noUploadPort) { + boardPort = BaseNoGui.getDiscoveryManager().find(PreferencesData.get("serial.port")); + } + + return new UploaderFactory().newUploader(target.getBoards().get(board), boardPort, noUploadPort); + } + + public boolean upload(SketchData data, Uploader uploader, String buildPath, String suggestedClassName, boolean usingProgrammer, boolean noUploadPort, List warningsAccumulator) throws Exception { + + if (uploader == null) + uploader = getUploaderByPreferences(noUploadPort); + + boolean success = false; + + if (uploader.requiresAuthorization() && !PreferencesData.has(uploader.getAuthorizationKey())) { + BaseNoGui.showError(tr("Authorization required"), + tr("No authorization data found"), null); + } + + boolean useNewWarningsAccumulator = false; + if (warningsAccumulator == null) { + warningsAccumulator = new LinkedList(); + useNewWarningsAccumulator = true; + } + + try { + success = uploader.uploadUsingPreferences(data.getFolder(), buildPath, suggestedClassName, usingProgrammer, warningsAccumulator); + } finally { + if (uploader.requiresAuthorization() && !success) { + PreferencesData.remove(uploader.getAuthorizationKey()); + } + } + + if (useNewWarningsAccumulator) { + for (String warning : warningsAccumulator) { + System.out.print(tr("Warning")); + System.out.print(": "); + System.out.println(warning); + } + } + + return success; + } + + +} diff --git a/arduino-core/src/cc/arduino/i18n/ExternalProcessOutputParser.java b/arduino-core/src/cc/arduino/i18n/ExternalProcessOutputParser.java new file mode 100644 index 00000000000..0bd4d2bc6d1 --- /dev/null +++ b/arduino-core/src/cc/arduino/i18n/ExternalProcessOutputParser.java @@ -0,0 +1,94 @@ +/* + * This file is part of Arduino. + * + * Copyright 2015 Arduino LLC (http://www.arduino.cc/) + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ + +package cc.arduino.i18n; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.*; +import java.util.stream.Collectors; + +public class ExternalProcessOutputParser { + + public Map parse(String s) { + if (!s.startsWith("===")) { + throw new IllegalArgumentException(s); + } + + s = s.substring(3); + + Map output = new HashMap<>(); + + String[] parts = s.split(" \\|\\|\\| "); + + output.put("msg", parts[0]); + output.put("args", parseArgs(parts[1])); + + return output; + } + + private Object[] parseArgs(String argsAsString) { + if (!argsAsString.startsWith("[") || !argsAsString.endsWith("]")) { + throw new IllegalArgumentException(argsAsString); + } + + argsAsString = argsAsString.substring(1, argsAsString.length() - 1); + if (argsAsString.isEmpty()) { + return new Object[0]; + } + + List args = new ArrayList<>(Arrays.asList(argsAsString.split(" "))); + List additionalArgs = addAsManyEmptyArgsAsEndingSpaces(argsAsString, args); + + for (int i = 0; i < args.size(); i++) { + try { + args.set(i, URLDecoder.decode(args.get(i), "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + args.addAll(additionalArgs); + + return args.toArray(); + } + + private List addAsManyEmptyArgsAsEndingSpaces(String argsAsString, List args) { + List additionalArgs = new LinkedList<>(); + + if (argsAsString.charAt(argsAsString.length() - 1) == ' ') { + String allArgsButEndingSpacesAsString = args.stream().collect(Collectors.joining(" ")); + String endingSpacesOnly = argsAsString.replace(allArgsButEndingSpacesAsString, ""); + for (int i = 0; i < endingSpacesOnly.length(); i++) { + additionalArgs.add(""); + } + } + return additionalArgs; + } +} diff --git a/arduino-core/src/cc/arduino/i18n/I18NAwareMessageConsumer.java b/arduino-core/src/cc/arduino/i18n/I18NAwareMessageConsumer.java new file mode 100644 index 00000000000..0c1fd98e3fa --- /dev/null +++ b/arduino-core/src/cc/arduino/i18n/I18NAwareMessageConsumer.java @@ -0,0 +1,70 @@ +/* + * This file is part of Arduino. + * + * Copyright 2015 Arduino LLC (http://www.arduino.cc/) + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ + +package cc.arduino.i18n; + +import processing.app.I18n; +import processing.app.debug.MessageConsumer; + +import java.io.PrintStream; +import java.util.Map; + +import static processing.app.I18n.tr; + +public class I18NAwareMessageConsumer implements MessageConsumer { + + private final PrintStream ps; + private final MessageConsumer parent; + private final ExternalProcessOutputParser parser; + + public I18NAwareMessageConsumer(PrintStream ps) { + this(ps, null); + } + + public I18NAwareMessageConsumer(PrintStream ps, MessageConsumer parent) { + this.ps = ps; + this.parent = parent; + this.parser = new ExternalProcessOutputParser(); + } + + @Override + public void message(String s) { + if (s.startsWith("===")) { + Map parsedMessage = parser.parse(s); + ps.println(I18n.format(tr((String) parsedMessage.get("msg")), (Object[]) parsedMessage.get("args"))); + return; + } + + if (parent != null) { + parent.message(s); + } else { + ps.println(s); + } + } +} diff --git a/arduino-core/src/cc/arduino/packages/Uploader.java b/arduino-core/src/cc/arduino/packages/Uploader.java index edd0a79bc26..3c99333fb46 100644 --- a/arduino-core/src/cc/arduino/packages/Uploader.java +++ b/arduino-core/src/cc/arduino/packages/Uploader.java @@ -1,5 +1,3 @@ -/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - /* Uploader - abstract uploading baseclass (common to both uisp and avrdude) Part of the Arduino project - http://www.arduino.cc/ diff --git a/arduino-core/src/cc/arduino/packages/uploaders/SSHUploader.java b/arduino-core/src/cc/arduino/packages/uploaders/SSHUploader.java index 1b72ec38453..5681c358960 100644 --- a/arduino-core/src/cc/arduino/packages/uploaders/SSHUploader.java +++ b/arduino-core/src/cc/arduino/packages/uploaders/SSHUploader.java @@ -29,6 +29,8 @@ package cc.arduino.packages.uploaders; +import cc.arduino.Compiler; +import cc.arduino.CompilerUtils; import cc.arduino.packages.BoardPort; import cc.arduino.packages.Uploader; import cc.arduino.packages.ssh.*; @@ -117,7 +119,7 @@ public boolean uploadUsingPreferences(File sourcePath, String buildPath, String if (!coreMissesRemoteUploadTool && mergedSketch.exists()) { sketchToCopy = mergedSketch; } else { - sketchToCopy = processing.app.debug.Compiler.findCompiledSketch(prefs); + sketchToCopy = new CompilerUtils().findCompiledSketch(prefs); } scpFiles(scp, ssh, sourcePath, sketchToCopy, warningsAccumulator); diff --git a/arduino-core/src/cc/arduino/packages/uploaders/SerialUploader.java b/arduino-core/src/cc/arduino/packages/uploaders/SerialUploader.java index 8470eb66df7..99a9e6286ed 100644 --- a/arduino-core/src/cc/arduino/packages/uploaders/SerialUploader.java +++ b/arduino-core/src/cc/arduino/packages/uploaders/SerialUploader.java @@ -1,5 +1,3 @@ -/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - /* BasicUploader - generic command line uploader implementation Part of the Arduino project - http://www.arduino.cc/ diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java index dfb3029fb2a..f607820b87c 100644 --- a/arduino-core/src/processing/app/BaseNoGui.java +++ b/arduino-core/src/processing/app/BaseNoGui.java @@ -1,6 +1,8 @@ package processing.app; +import cc.arduino.Compiler; import cc.arduino.Constants; +import cc.arduino.UploaderUtils; import cc.arduino.contributions.GPGDetachedSignatureVerifier; import cc.arduino.contributions.SignatureVerificationFailedException; import cc.arduino.contributions.libraries.LibrariesIndexer; @@ -13,7 +15,6 @@ import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.logging.impl.LogFactoryImpl; import org.apache.commons.logging.impl.NoOpLog; -import processing.app.debug.Compiler; import processing.app.debug.*; import processing.app.helpers.*; import processing.app.helpers.filefilters.OnlyDirs; @@ -504,16 +505,16 @@ static public void init(String[] args) throws Exception { if (!data.getFolder().exists()) { showError(tr("No sketch"), tr("Can't find the sketch in the specified path"), null); } - String suggestedClassName = Compiler.build(data, tempBuildFolder.getAbsolutePath(), tempBuildFolder, null, parser.isDoVerboseBuild(), false); + String suggestedClassName = new Compiler(data, tempBuildFolder.getAbsolutePath()).build(null, false); if (suggestedClassName == null) { showError(tr("Error while verifying"), tr("An error occurred while verifying the sketch"), null); } showMessage(tr("Done compiling"), tr("Done compiling")); - Uploader uploader = Compiler.getUploaderByPreferences(parser.isNoUploadPort()); + Uploader uploader = new UploaderUtils().getUploaderByPreferences(parser.isNoUploadPort()); if (uploader.requiresAuthorization() && !PreferencesData.has(uploader.getAuthorizationKey())) showError("...", "...", null); try { - success = Compiler.upload(data, uploader, tempBuildFolder.getAbsolutePath(), suggestedClassName, parser.isDoUseProgrammer(), parser.isNoUploadPort(), warningsAccumulator); + success = new UploaderUtils().upload(data, uploader, tempBuildFolder.getAbsolutePath(), suggestedClassName, parser.isDoUseProgrammer(), parser.isNoUploadPort(), warningsAccumulator); showMessage(tr("Done uploading"), tr("Done uploading")); } finally { if (uploader.requiresAuthorization() && !success) { @@ -550,7 +551,7 @@ static public void init(String[] args) throws Exception { // if (!data.getFolder().exists()) showError(...); // String ... = Compiler.build(data, tempBuildFolder.getAbsolutePath(), tempBuildFolder, null, verbose); if (!data.getFolder().exists()) showError(tr("No sketch"), tr("Can't find the sketch in the specified path"), null); - String suggestedClassName = Compiler.build(data, tempBuildFolder.getAbsolutePath(), tempBuildFolder, null, parser.isDoVerboseBuild(), false); + String suggestedClassName = new Compiler(data, tempBuildFolder.getAbsolutePath()).build(null, false); if (suggestedClassName == null) showError(tr("Error while verifying"), tr("An error occurred while verifying the sketch"), null); showMessage(tr("Done compiling"), tr("Done compiling")); } catch (Exception e) { diff --git a/arduino-core/src/processing/app/debug/MessageConsumer.java b/arduino-core/src/processing/app/debug/MessageConsumer.java index 5e204294333..02d458f8516 100644 --- a/arduino-core/src/processing/app/debug/MessageConsumer.java +++ b/arduino-core/src/processing/app/debug/MessageConsumer.java @@ -26,17 +26,17 @@ /** * Interface for dealing with parser/compiler output. - *

+ *

* Different instances of MessageStream need to do different things with * messages. In particular, a stream instance used for parsing output from * the compiler compiler has to interpret its messages differently than one * parsing output from the runtime. - *

+ *

* Classes which consume messages and do something with them * should implement this interface. */ public interface MessageConsumer { - public void message(String s); + void message(String s); } diff --git a/arduino-core/src/processing/app/debug/Compiler.java b/arduino-core/src/processing/app/debug/OldCompiler.java similarity index 99% rename from arduino-core/src/processing/app/debug/Compiler.java rename to arduino-core/src/processing/app/debug/OldCompiler.java index ad1cb914f62..0587c0d8a12 100644 --- a/arduino-core/src/processing/app/debug/Compiler.java +++ b/arduino-core/src/processing/app/debug/OldCompiler.java @@ -61,7 +61,8 @@ import static processing.app.I18n.tr; -public class Compiler implements MessageConsumer { +@Deprecated +public class OldCompiler implements MessageConsumer { /** * File inside the build directory that contains the build options @@ -96,7 +97,7 @@ static public String build(SketchData data, String buildPath, File tempBuildFold tr("Bad sketch primary file or bad sketch directory structure"), null); String primaryClassName = data.getName() + ".cpp"; - Compiler compiler = new Compiler(data, buildPath, primaryClassName); + OldCompiler compiler = new OldCompiler(data, buildPath, primaryClassName); File buildPrefsFile = new File(buildPath, BUILD_PREFS_FILE); String newBuildPrefs = compiler.buildPrefsString(); @@ -208,7 +209,7 @@ static public File findCompiledSketch(PreferencesMap prefs) throws PreferencesMa * @param _buildPath Where the temporary files live and will be built from. * @param _primaryClassName the name of the combined sketch file w/ extension */ - public Compiler(SketchData _sketch, String _buildPath, String _primaryClassName) + public OldCompiler(SketchData _sketch, String _buildPath, String _primaryClassName) throws RunnerException { sketch = _sketch; prefs = createBuildPreferences(_buildPath, _primaryClassName); @@ -808,7 +809,7 @@ private void execAsynchronously(String[] command) throws RunnerException { @Override protected Thread createPump(InputStream is, OutputStream os, boolean closeWhenExhausted) { - final Thread result = new Thread(new MyStreamPumper(is, Compiler.this)); + final Thread result = new Thread(new MyStreamPumper(is, OldCompiler.this)); result.setDaemon(true); return result; diff --git a/arduino-core/src/processing/app/helpers/CommandlineParser.java b/arduino-core/src/processing/app/helpers/CommandlineParser.java index e8554f4dfe0..6d93dcc7e4f 100644 --- a/arduino-core/src/processing/app/helpers/CommandlineParser.java +++ b/arduino-core/src/processing/app/helpers/CommandlineParser.java @@ -277,6 +277,7 @@ private void processPrefArgument(String arg) { BaseNoGui.showError(null, I18n.format(tr("{0}: Invalid argument to --pref, should be of the form \"pref=value\""), arg), 3); PreferencesData.set(split[0], split[1]); + PreferencesData.set("build_properties_custom." + split[0], split[1]); } public boolean isDoVerboseBuild() { diff --git a/build/arduino-builder-linux32-1.0.0-beta3.tar.bz2.sha b/build/arduino-builder-linux32-1.0.0-beta3.tar.bz2.sha new file mode 100644 index 00000000000..f56ddd3f43f --- /dev/null +++ b/build/arduino-builder-linux32-1.0.0-beta3.tar.bz2.sha @@ -0,0 +1 @@ +4b72651bf25dc056c430c9160e7c6a4aa9909214 diff --git a/build/arduino-builder-linux64-1.0.0-beta3.tar.bz2.sha b/build/arduino-builder-linux64-1.0.0-beta3.tar.bz2.sha new file mode 100644 index 00000000000..fd8e17bd426 --- /dev/null +++ b/build/arduino-builder-linux64-1.0.0-beta3.tar.bz2.sha @@ -0,0 +1 @@ +68d07baf4cb12f270ca1fe6c669018695614a304 diff --git a/build/arduino-builder-macosx-1.0.0-beta3.tar.bz2.sha b/build/arduino-builder-macosx-1.0.0-beta3.tar.bz2.sha new file mode 100644 index 00000000000..b3d691b988a --- /dev/null +++ b/build/arduino-builder-macosx-1.0.0-beta3.tar.bz2.sha @@ -0,0 +1 @@ +15c8230a431669e629e9cbb635df6d90731cf144 diff --git a/build/arduino-builder-windows-1.0.0-beta3.zip.sha b/build/arduino-builder-windows-1.0.0-beta3.zip.sha new file mode 100644 index 00000000000..280d614455e --- /dev/null +++ b/build/arduino-builder-windows-1.0.0-beta3.zip.sha @@ -0,0 +1 @@ +5979a9ea20c7b982433a1119875aa6518b859bb6 diff --git a/build/build.xml b/build/build.xml index e9d8995d1e4..d4841168eac 100644 --- a/build/build.xml +++ b/build/build.xml @@ -71,6 +71,8 @@ + + @@ -372,6 +374,21 @@ + + + + + + + + + + + + + + + @@ -483,6 +500,9 @@ + + + @@ -541,6 +561,21 @@ + + + + + + + + + + + + + + + @@ -557,6 +592,21 @@ + + + + + + + + + + + + + + + @@ -762,6 +812,21 @@ + + + + + + + + + + + + + + +