PageRenderTime 30ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/arduino-core/src/processing/app/debug/Compiler.java

https://github.com/ricklon/Arduino
Java | 1322 lines | 1086 code | 123 blank | 113 comment | 131 complexity | 799a86f8519eff6688e2b96b293b839c MD5 | raw file
  1. /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
  2. /*
  3. Part of the Processing project - http://processing.org
  4. Copyright (c) 2004-08 Ben Fry and Casey Reas
  5. Copyright (c) 2001-04 Massachusetts Institute of Technology
  6. This program is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2 of the License, or
  9. (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program; if not, write to the Free Software Foundation,
  16. Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17. */
  18. package processing.app.debug;
  19. import cc.arduino.Constants;
  20. import cc.arduino.MyStreamPumper;
  21. import cc.arduino.contributions.packages.ContributedPlatform;
  22. import cc.arduino.contributions.packages.ContributedTool;
  23. import cc.arduino.packages.BoardPort;
  24. import cc.arduino.packages.Uploader;
  25. import cc.arduino.packages.UploaderFactory;
  26. import cc.arduino.packages.uploaders.MergeSketchWithBooloader;
  27. import cc.arduino.utils.Pair;
  28. import org.apache.commons.compress.utils.IOUtils;
  29. import org.apache.commons.exec.CommandLine;
  30. import org.apache.commons.exec.DefaultExecutor;
  31. import org.apache.commons.exec.PumpStreamHandler;
  32. import processing.app.*;
  33. import processing.app.helpers.FileUtils;
  34. import processing.app.helpers.PreferencesMap;
  35. import processing.app.helpers.PreferencesMapException;
  36. import processing.app.helpers.StringReplacer;
  37. import processing.app.helpers.filefilters.OnlyDirs;
  38. import processing.app.legacy.PApplet;
  39. import processing.app.packages.LegacyUserLibrary;
  40. import processing.app.packages.LibraryList;
  41. import processing.app.packages.UserLibrary;
  42. import processing.app.preproc.PdePreprocessor;
  43. import processing.app.tools.DoubleQuotedArgumentsOnWindowsCommandLine;
  44. import java.io.*;
  45. import java.nio.file.Files;
  46. import java.nio.file.Path;
  47. import java.nio.file.Paths;
  48. import java.nio.file.StandardCopyOption;
  49. import java.util.*;
  50. import java.util.stream.Collectors;
  51. import java.util.stream.Stream;
  52. import static processing.app.I18n.tr;
  53. public class Compiler implements MessageConsumer {
  54. /**
  55. * File inside the build directory that contains the build options
  56. * used for the last build.
  57. */
  58. static final public String BUILD_PREFS_FILE = "buildprefs.txt";
  59. private static final int ADDITIONAL_FILES_COPY_MAX_DEPTH = 5;
  60. private SketchData sketch;
  61. private PreferencesMap prefs;
  62. private boolean verbose;
  63. private boolean saveHex;
  64. private List<File> objectFiles;
  65. private boolean sketchIsCompiled;
  66. private RunnerException exception;
  67. /**
  68. * Listener interface for progress update on the GUI
  69. */
  70. public interface ProgressListener {
  71. void progress(int percent);
  72. }
  73. private ProgressListener progressListener;
  74. static public String build(SketchData data, String buildPath, File tempBuildFolder, ProgressListener progListener, boolean verbose, boolean save) throws RunnerException, PreferencesMapException {
  75. if (SketchData.checkSketchFile(data.getPrimaryFile()) == null)
  76. BaseNoGui.showError(tr("Bad file selected"),
  77. tr("Bad sketch primary file or bad sketch directory structure"), null);
  78. String primaryClassName = data.getName() + ".cpp";
  79. Compiler compiler = new Compiler(data, buildPath, primaryClassName);
  80. File buildPrefsFile = new File(buildPath, BUILD_PREFS_FILE);
  81. String newBuildPrefs = compiler.buildPrefsString();
  82. // Do a forced cleanup (throw everything away) if the previous
  83. // build settings do not match the previous ones
  84. boolean prefsChanged = compiler.buildPreferencesChanged(buildPrefsFile, newBuildPrefs);
  85. compiler.cleanup(prefsChanged, tempBuildFolder);
  86. if (prefsChanged) {
  87. PrintWriter out = null;
  88. try {
  89. out = new PrintWriter(buildPrefsFile);
  90. out.print(newBuildPrefs);
  91. } catch (IOException e) {
  92. System.err.println(tr("Could not write build preferences file"));
  93. } finally {
  94. IOUtils.closeQuietly(out);
  95. }
  96. }
  97. compiler.setProgressListener(progListener);
  98. // compile the program. errors will happen as a RunnerException
  99. // that will bubble up to whomever called build().
  100. try {
  101. if (compiler.compile(verbose, save)) {
  102. compiler.size(compiler.getBuildPreferences());
  103. return primaryClassName;
  104. }
  105. } catch (RunnerException e) {
  106. // when the compile fails, take this opportunity to show
  107. // any helpful info possible before throwing the exception
  108. compiler.adviseDuplicateLibraries();
  109. throw e;
  110. }
  111. return null;
  112. }
  113. static public Uploader getUploaderByPreferences(boolean noUploadPort) {
  114. TargetPlatform target = BaseNoGui.getTargetPlatform();
  115. String board = PreferencesData.get("board");
  116. BoardPort boardPort = null;
  117. if (!noUploadPort) {
  118. boardPort = BaseNoGui.getDiscoveryManager().find(PreferencesData.get("serial.port"));
  119. }
  120. return new UploaderFactory().newUploader(target.getBoards().get(board), boardPort, noUploadPort);
  121. }
  122. static public boolean upload(SketchData data, Uploader uploader, String buildPath, String suggestedClassName, boolean usingProgrammer, boolean noUploadPort, List<String> warningsAccumulator) throws Exception {
  123. if (uploader == null)
  124. uploader = getUploaderByPreferences(noUploadPort);
  125. boolean success = false;
  126. if (uploader.requiresAuthorization() && !PreferencesData.has(uploader.getAuthorizationKey())) {
  127. BaseNoGui.showError(tr("Authorization required"),
  128. tr("No authorization data found"), null);
  129. }
  130. boolean useNewWarningsAccumulator = false;
  131. if (warningsAccumulator == null) {
  132. warningsAccumulator = new LinkedList<String>();
  133. useNewWarningsAccumulator = true;
  134. }
  135. try {
  136. success = uploader.uploadUsingPreferences(data.getFolder(), buildPath, suggestedClassName, usingProgrammer, warningsAccumulator);
  137. } finally {
  138. if (uploader.requiresAuthorization() && !success) {
  139. PreferencesData.remove(uploader.getAuthorizationKey());
  140. }
  141. }
  142. if (useNewWarningsAccumulator) {
  143. for (String warning : warningsAccumulator) {
  144. System.out.print(tr("Warning"));
  145. System.out.print(": ");
  146. System.out.println(warning);
  147. }
  148. }
  149. return success;
  150. }
  151. static public File findCompiledSketch(PreferencesMap prefs) throws PreferencesMapException {
  152. List<String> paths = Arrays.asList(
  153. "{build.path}/sketch/{build.project_name}.with_bootloader.hex",
  154. "{build.path}/sketch/{build.project_name}.hex",
  155. "{build.path}/{build.project_name}.with_bootloader.hex",
  156. "{build.path}/{build.project_name}.hex",
  157. "{build.path}/sketch/{build.project_name}.bin",
  158. "{build.path}/{build.project_name}.bin"
  159. );
  160. Optional<File> sketch = paths.stream().
  161. map(path -> StringReplacer.replaceFromMapping(path, prefs)).
  162. map(File::new).
  163. filter(File::exists).
  164. findFirst();
  165. return sketch.orElseThrow(() -> new IllegalStateException(tr("No compiled sketch found")));
  166. }
  167. /**
  168. * Create a new Compiler
  169. * @param _sketch Sketch object to be compiled.
  170. * @param _buildPath Where the temporary files live and will be built from.
  171. * @param _primaryClassName the name of the combined sketch file w/ extension
  172. */
  173. public Compiler(SketchData _sketch, String _buildPath, String _primaryClassName)
  174. throws RunnerException {
  175. sketch = _sketch;
  176. prefs = createBuildPreferences(_buildPath, _primaryClassName);
  177. // provide access to the source tree
  178. prefs.put("build.source.path", _sketch.getFolder().getAbsolutePath());
  179. // Start with an empty progress listener
  180. progressListener = new ProgressListener() {
  181. @Override
  182. public void progress(int percent) {
  183. }
  184. };
  185. }
  186. /**
  187. * Check if the build preferences used on the previous build in
  188. * buildPath match the ones given.
  189. */
  190. protected boolean buildPreferencesChanged(File buildPrefsFile, String newBuildPrefs) {
  191. // No previous build, so no match
  192. if (!buildPrefsFile.exists())
  193. return true;
  194. String previousPrefs;
  195. try {
  196. previousPrefs = FileUtils.readFileToString(buildPrefsFile);
  197. } catch (IOException e) {
  198. System.err.println(tr("Could not read prevous build preferences file, rebuilding all"));
  199. return true;
  200. }
  201. if (!previousPrefs.equals(newBuildPrefs)) {
  202. System.out.println(tr("Build options changed, rebuilding all"));
  203. return true;
  204. } else {
  205. return false;
  206. }
  207. }
  208. /**
  209. * Returns the build preferences of the given compiler as a string.
  210. * Only includes build-specific preferences, to make sure unrelated
  211. * preferences don't cause a rebuild (in particular preferences that
  212. * change on every start, like last.ide.xxx.daterun). */
  213. protected String buildPrefsString() {
  214. PreferencesMap buildPrefs = getBuildPreferences();
  215. String res = "";
  216. SortedSet<String> treeSet = new TreeSet<String>(buildPrefs.keySet());
  217. for (String k : treeSet) {
  218. if (k.startsWith("build.") || k.startsWith("compiler.") || k.startsWith("recipes."))
  219. res += k + " = " + buildPrefs.get(k) + "\n";
  220. }
  221. return res;
  222. }
  223. protected void setProgressListener(ProgressListener _progressListener) {
  224. progressListener = (_progressListener == null ?
  225. new ProgressListener() {
  226. @Override
  227. public void progress(int percent) {
  228. }
  229. } : _progressListener);
  230. }
  231. /**
  232. * Cleanup temporary files used during a build/run.
  233. */
  234. protected void cleanup(boolean force, File tempBuildFolder) {
  235. // if the java runtime is holding onto any files in the build dir, we
  236. // won't be able to delete them, so we need to force a gc here
  237. System.gc();
  238. if (force) {
  239. // delete the entire directory and all contents
  240. // when we know something changed and all objects
  241. // need to be recompiled, or if the board does not
  242. // use setting build.dependency
  243. //Base.removeDir(tempBuildFolder);
  244. // note that we can't remove the builddir itself, otherwise
  245. // the next time we start up, internal runs using Runner won't
  246. // work because the build dir won't exist at startup, so the classloader
  247. // will ignore the fact that that dir is in the CLASSPATH in run.sh
  248. BaseNoGui.removeDescendants(tempBuildFolder);
  249. } else {
  250. // delete only stale source files, from the previously
  251. // compiled sketch. This allows multiple windows to be
  252. // used. Keep everything else, which might be reusable
  253. if (tempBuildFolder.exists()) {
  254. String files[] = tempBuildFolder.list();
  255. if (files != null) {
  256. for (String file : files) {
  257. if (file.endsWith(".c") || file.endsWith(".cpp") || file.endsWith(".s")) {
  258. File deleteMe = new File(tempBuildFolder, file);
  259. if (!deleteMe.delete()) {
  260. System.err.println("Could not delete " + deleteMe);
  261. }
  262. }
  263. }
  264. }
  265. }
  266. }
  267. // Create a fresh applet folder (needed before preproc is run below)
  268. //tempBuildFolder.mkdirs();
  269. }
  270. protected void size(PreferencesMap prefs) throws RunnerException {
  271. String maxTextSizeString = prefs.get("upload.maximum_size");
  272. String maxDataSizeString = prefs.get("upload.maximum_data_size");
  273. if (maxTextSizeString == null)
  274. return;
  275. long maxTextSize = Integer.parseInt(maxTextSizeString);
  276. long maxDataSize = -1;
  277. if (maxDataSizeString != null)
  278. maxDataSize = Integer.parseInt(maxDataSizeString);
  279. Sizer sizer = new Sizer(prefs);
  280. long[] sizes;
  281. try {
  282. sizes = sizer.computeSize();
  283. } catch (RunnerException e) {
  284. System.err.println(I18n.format(tr("Couldn't determine program size: {0}"),
  285. e.getMessage()));
  286. return;
  287. }
  288. long textSize = sizes[0];
  289. long dataSize = sizes[1];
  290. System.out.println();
  291. System.out.println(I18n
  292. .format(tr("Sketch uses {0} bytes ({2}%%) of program storage space. Maximum is {1} bytes."),
  293. textSize, maxTextSize, textSize * 100 / maxTextSize));
  294. if (dataSize >= 0) {
  295. if (maxDataSize > 0) {
  296. System.out
  297. .println(I18n
  298. .format(
  299. tr("Global variables use {0} bytes ({2}%%) of dynamic memory, leaving {3} bytes for local variables. Maximum is {1} bytes."),
  300. dataSize, maxDataSize, dataSize * 100 / maxDataSize,
  301. maxDataSize - dataSize));
  302. } else {
  303. System.out.println(I18n
  304. .format(tr("Global variables use {0} bytes of dynamic memory."), dataSize));
  305. }
  306. }
  307. if (textSize > maxTextSize)
  308. throw new RunnerException(
  309. tr("Sketch too big; see http://www.arduino.cc/en/Guide/Troubleshooting#size for tips on reducing it."));
  310. if (maxDataSize > 0 && dataSize > maxDataSize)
  311. throw new RunnerException(
  312. tr("Not enough memory; see http://www.arduino.cc/en/Guide/Troubleshooting#size for tips on reducing your footprint."));
  313. int warnDataPercentage = Integer.parseInt(prefs.get("build.warn_data_percentage"));
  314. if (maxDataSize > 0 && dataSize > maxDataSize*warnDataPercentage/100)
  315. System.err.println(tr("Low memory available, stability problems may occur."));
  316. }
  317. /**
  318. * Compile sketch.
  319. * @param _verbose
  320. *
  321. * @return true if successful.
  322. * @throws RunnerException Only if there's a problem. Only then.
  323. */
  324. public boolean compile(boolean _verbose, boolean _save) throws RunnerException, PreferencesMapException {
  325. File sketchBuildFolder = new File(prefs.get("build.path"), "sketch");
  326. if (!sketchBuildFolder.exists() && !sketchBuildFolder.mkdirs()) {
  327. throw new RunnerException("Unable to create folder " + sketchBuildFolder);
  328. }
  329. preprocess(sketchBuildFolder.getAbsolutePath());
  330. verbose = _verbose || PreferencesData.getBoolean("build.verbose");
  331. saveHex = _save;
  332. sketchIsCompiled = false;
  333. // Hook runs at Start of Compilation
  334. runActions("hooks.prebuild", prefs);
  335. objectFiles = new ArrayList<File>();
  336. // 0. include paths for core + all libraries
  337. progressListener.progress(20);
  338. List<File> includeFolders = new ArrayList<File>();
  339. includeFolders.add(prefs.getFile("build.core.path"));
  340. includeFolders.add(sketch.getFolder());
  341. if (prefs.getFile("build.variant.path") != null)
  342. includeFolders.add(prefs.getFile("build.variant.path"));
  343. for (UserLibrary lib : importedLibraries) {
  344. if (verbose) {
  345. String legacy = "";
  346. if (lib instanceof LegacyUserLibrary) {
  347. legacy = "(legacy)";
  348. }
  349. if (lib.getParsedVersion() == null) {
  350. System.out.println(I18n.format(tr("Using library {0} in folder: {1} {2}"), lib.getName(), lib.getInstalledFolder(), legacy));
  351. } else {
  352. System.out.println(I18n.format(tr("Using library {0} at version {1} in folder: {2} {3}"), lib.getName(), lib.getParsedVersion(), lib.getInstalledFolder(), legacy));
  353. }
  354. }
  355. includeFolders.add(lib.getSrcFolder());
  356. }
  357. if (verbose) {
  358. System.out.println();
  359. }
  360. List<String> archs = new ArrayList<String>();
  361. archs.add(BaseNoGui.getTargetPlatform().getId());
  362. if (prefs.containsKey("architecture.override_check")) {
  363. String[] overrides = prefs.get("architecture.override_check").split(",");
  364. archs.addAll(Arrays.asList(overrides));
  365. }
  366. for (UserLibrary lib : importedLibraries) {
  367. if (!lib.supportsArchitecture(archs)) {
  368. System.err.println(I18n
  369. .format(tr("WARNING: library {0} claims to run on {1} "
  370. + "architecture(s) and may be incompatible with your"
  371. + " current board which runs on {2} architecture(s)."), lib
  372. .getName(), lib.getArchitectures(), archs));
  373. System.err.println();
  374. }
  375. }
  376. runActions("hooks.sketch.prebuild", prefs);
  377. // 1. compile the sketch (already in the buildPath)
  378. progressListener.progress(20);
  379. compileSketch(includeFolders, sketchBuildFolder);
  380. sketchIsCompiled = true;
  381. runActions("hooks.sketch.postbuild", prefs);
  382. runActions("hooks.libraries.prebuild", prefs);
  383. // 2. compile the libraries, outputting .o files to: <buildPath>/<library>/
  384. // Doesn't really use configPreferences
  385. progressListener.progress(30);
  386. compileLibraries(includeFolders);
  387. runActions("hooks.libraries.postbuild", prefs);
  388. runActions("hooks.core.prebuild", prefs);
  389. // 3. compile the core, outputting .o files to <buildPath> and then
  390. // collecting them into the core.a library file.
  391. progressListener.progress(40);
  392. compileCore();
  393. runActions("hooks.core.postbuild", prefs);
  394. runActions("hooks.linking.prelink", prefs);
  395. // 4. link it all together into the .elf file
  396. progressListener.progress(50);
  397. compileLink();
  398. runActions("hooks.linking.postlink", prefs);
  399. runActions("hooks.objcopy.preobjcopy", prefs);
  400. // 5. run objcopy to generate output files
  401. progressListener.progress(60);
  402. List<String> objcopyPatterns = new ArrayList<String>();
  403. for (String key : prefs.keySet()) {
  404. if (key.startsWith("recipe.objcopy.") && key.endsWith(".pattern"))
  405. objcopyPatterns.add(key);
  406. }
  407. Collections.sort(objcopyPatterns);
  408. for (String recipe : objcopyPatterns) {
  409. runRecipe(recipe);
  410. }
  411. runActions("hooks.objcopy.postobjcopy", prefs);
  412. progressListener.progress(70);
  413. try {
  414. mergeSketchWithBootloaderIfAppropriate(sketch.getName() + ".cpp", prefs);
  415. } catch (IOException e) {
  416. e.printStackTrace();
  417. // ignore
  418. }
  419. // 7. save the hex file
  420. if (saveHex) {
  421. runActions("hooks.savehex.presavehex", prefs);
  422. progressListener.progress(80);
  423. saveHex();
  424. runActions("hooks.savehex.postsavehex", prefs);
  425. }
  426. progressListener.progress(90);
  427. // Hook runs at End of Compilation
  428. runActions("hooks.postbuild", prefs);
  429. adviseDuplicateLibraries();
  430. return true;
  431. }
  432. private void adviseDuplicateLibraries() {
  433. if (importedDuplicateHeaders == null) {
  434. return;
  435. }
  436. for (int i=0; i < importedDuplicateHeaders.size(); i++) {
  437. System.out.println(I18n.format(tr("Multiple libraries were found for \"{0}\""),
  438. importedDuplicateHeaders.get(i)));
  439. boolean first = true;
  440. for (UserLibrary lib : importedDuplicateLibraries.get(i)) {
  441. if (first) {
  442. System.out.println(I18n.format(tr(" Used: {0}"),
  443. lib.getInstalledFolder().getPath()));
  444. first = false;
  445. } else {
  446. System.out.println(I18n.format(tr(" Not used: {0}"),
  447. lib.getInstalledFolder().getPath()));
  448. }
  449. }
  450. }
  451. }
  452. private PreferencesMap createBuildPreferences(String _buildPath,
  453. String _primaryClassName)
  454. throws RunnerException {
  455. if (BaseNoGui.getBoardPreferences() == null) {
  456. RunnerException re = new RunnerException(
  457. tr("No board selected; please choose a board from the Tools > Board menu."));
  458. re.hideStackTrace();
  459. throw re;
  460. }
  461. // Check if the board needs a platform from another package
  462. TargetPlatform targetPlatform = BaseNoGui.getTargetPlatform();
  463. TargetPlatform corePlatform = null;
  464. PreferencesMap boardPreferences = BaseNoGui.getBoardPreferences();
  465. String core = boardPreferences.get("build.core", "arduino");
  466. if (core.contains(":")) {
  467. String[] split = core.split(":");
  468. core = split[1];
  469. corePlatform = BaseNoGui.getTargetPlatform(split[0], targetPlatform.getId());
  470. if (corePlatform == null) {
  471. RunnerException re = new RunnerException(I18n
  472. .format(tr("Selected board depends on '{0}' core (not installed)."),
  473. split[0]));
  474. re.hideStackTrace();
  475. throw re;
  476. }
  477. }
  478. // Merge all the global preference configuration in order of priority
  479. PreferencesMap buildPref = new PreferencesMap();
  480. buildPref.putAll(PreferencesData.getMap());
  481. if (corePlatform != null) {
  482. buildPref.putAll(corePlatform.getPreferences());
  483. }
  484. buildPref.putAll(targetPlatform.getPreferences());
  485. buildPref.putAll(BaseNoGui.getBoardPreferences());
  486. for (String k : buildPref.keySet()) {
  487. if (buildPref.get(k) == null) {
  488. buildPref.put(k, "");
  489. }
  490. }
  491. buildPref.put("build.path", _buildPath);
  492. buildPref.put("build.project_name", _primaryClassName);
  493. buildPref.put("build.arch", targetPlatform.getId().toUpperCase());
  494. // Platform.txt should define its own compiler.path. For
  495. // compatibility with earlier 1.5 versions, we define a (ugly,
  496. // avr-specific) default for it, but this should be removed at some
  497. // point.
  498. if (!buildPref.containsKey("compiler.path")) {
  499. System.err.println(tr("Third-party platform.txt does not define compiler.path. Please report this to the third-party hardware maintainer."));
  500. buildPref.put("compiler.path", BaseNoGui.getAvrBasePath());
  501. }
  502. TargetPlatform referencePlatform = null;
  503. if (corePlatform != null) {
  504. referencePlatform = corePlatform;
  505. } else {
  506. referencePlatform = targetPlatform;
  507. }
  508. buildPref.put("build.platform.path", referencePlatform.getFolder().getAbsolutePath());
  509. // Core folder
  510. File coreFolder = new File(referencePlatform.getFolder(), "cores");
  511. coreFolder = new File(coreFolder, core);
  512. buildPref.put("build.core", core);
  513. buildPref.put("build.core.path", coreFolder.getAbsolutePath());
  514. // System Folder
  515. File systemFolder = referencePlatform.getFolder();
  516. systemFolder = new File(systemFolder, "system");
  517. buildPref.put("build.system.path", systemFolder.getAbsolutePath());
  518. // Variant Folder
  519. String variant = buildPref.get("build.variant");
  520. if (variant != null) {
  521. TargetPlatform t;
  522. if (!variant.contains(":")) {
  523. t = targetPlatform;
  524. } else {
  525. String[] split = variant.split(":", 2);
  526. t = BaseNoGui.getTargetPlatform(split[0], targetPlatform.getId());
  527. variant = split[1];
  528. }
  529. File variantFolder = new File(t.getFolder(), "variants");
  530. variantFolder = new File(variantFolder, variant);
  531. buildPref.put("build.variant.path", variantFolder.getAbsolutePath());
  532. } else {
  533. buildPref.put("build.variant.path", "");
  534. }
  535. ContributedPlatform installedPlatform = BaseNoGui.indexer.getInstalled(referencePlatform.getContainerPackage().getId(), referencePlatform.getId());
  536. if (installedPlatform != null) {
  537. List<ContributedTool> tools = installedPlatform.getResolvedTools();
  538. BaseNoGui.createToolPreferences(tools, false);
  539. }
  540. // Build Time
  541. GregorianCalendar cal = new GregorianCalendar();
  542. long current = new Date().getTime() / 1000;
  543. long timezone = cal.get(Calendar.ZONE_OFFSET) / 1000;
  544. long daylight = cal.get(Calendar.DST_OFFSET) / 1000;
  545. buildPref.put("extra.time.utc", Long.toString(current));
  546. buildPref.put("extra.time.local", Long.toString(current + timezone + daylight));
  547. buildPref.put("extra.time.zone", Long.toString(timezone));
  548. buildPref.put("extra.time.dst", Long.toString(daylight));
  549. List<Map.Entry<String, String>> unsetPrefs = buildPref.entrySet().stream()
  550. .filter(entry -> Constants.PREF_REMOVE_PLACEHOLDER.equals(entry.getValue()))
  551. .collect(Collectors.toList());
  552. buildPref.entrySet().stream()
  553. .filter(entry -> {
  554. return unsetPrefs.stream()
  555. .filter(unsetPrefEntry -> entry.getValue().contains(unsetPrefEntry.getKey()))
  556. .count() > 0;
  557. })
  558. .forEach(invalidEntry -> buildPref.put(invalidEntry.getKey(), ""));
  559. return buildPref;
  560. }
  561. private List<File> compileFiles(File outputPath, File sourcePath,
  562. boolean recurse, List<File> includeFolders)
  563. throws RunnerException, PreferencesMapException {
  564. List<File> sSources = findFilesInFolder(sourcePath, "S", recurse);
  565. List<File> cSources = findFilesInFolder(sourcePath, "c", recurse);
  566. List<File> cppSources = findFilesInFolder(sourcePath, "cpp", recurse);
  567. List<File> objectPaths = new ArrayList<File>();
  568. for (File file : sSources) {
  569. File objectFile = new File(outputPath, file.getName() + ".o");
  570. objectPaths.add(objectFile);
  571. String[] cmd = getCommandCompilerByRecipe(includeFolders, file, objectFile, "recipe.S.o.pattern");
  572. execAsynchronously(cmd);
  573. }
  574. for (File file : cSources) {
  575. File objectFile = new File(outputPath, file.getName() + ".o");
  576. File dependFile = new File(outputPath, file.getName() + ".d");
  577. objectPaths.add(objectFile);
  578. if (isAlreadyCompiled(file, objectFile, dependFile, prefs))
  579. continue;
  580. String[] cmd = getCommandCompilerByRecipe(includeFolders, file, objectFile, "recipe.c.o.pattern");
  581. execAsynchronously(cmd);
  582. }
  583. for (File file : cppSources) {
  584. File objectFile = new File(outputPath, file.getName() + ".o");
  585. File dependFile = new File(outputPath, file.getName() + ".d");
  586. objectPaths.add(objectFile);
  587. if (isAlreadyCompiled(file, objectFile, dependFile, prefs))
  588. continue;
  589. String[] cmd = getCommandCompilerByRecipe(includeFolders, file, objectFile, "recipe.cpp.o.pattern");
  590. execAsynchronously(cmd);
  591. }
  592. return objectPaths;
  593. }
  594. /**
  595. * Strip escape sequences used in makefile dependency files (.d)
  596. * https://github.com/arduino/Arduino/issues/2255#issuecomment-57645845
  597. *
  598. * @param line
  599. * @return
  600. */
  601. protected static String unescapeDepFile(String line) {
  602. // Replaces: "\\" -> "\"
  603. // Replaces: "\ " -> " "
  604. // Replaces: "\#" -> "#"
  605. line = line.replaceAll("\\\\([ #\\\\])", "$1");
  606. // Replaces: "$$" -> "$"
  607. line = line.replace("$$", "$");
  608. return line;
  609. }
  610. private boolean isAlreadyCompiled(File src, File obj, File dep, Map<String, String> prefs) {
  611. boolean ret=true;
  612. BufferedReader reader = null;
  613. try {
  614. //System.out.println("\n isAlreadyCompiled: begin checks: " + obj.getPath());
  615. if (!obj.exists()) return false; // object file (.o) does not exist
  616. if (!dep.exists()) return false; // dep file (.d) does not exist
  617. long src_modified = src.lastModified();
  618. long obj_modified = obj.lastModified();
  619. if (src_modified >= obj_modified) return false; // source modified since object compiled
  620. if (src_modified >= dep.lastModified()) return false; // src modified since dep compiled
  621. reader = new BufferedReader(new FileReader(dep.getPath()));
  622. String line;
  623. boolean need_obj_parse = true;
  624. while ((line = reader.readLine()) != null) {
  625. if (line.endsWith("\\")) {
  626. line = line.substring(0, line.length() - 1);
  627. }
  628. line = line.trim();
  629. line = unescapeDepFile(line);
  630. if (line.length() == 0) continue; // ignore blank lines
  631. if (need_obj_parse) {
  632. // line is supposed to be the object file - make sure it really is!
  633. if (line.endsWith(":")) {
  634. line = line.substring(0, line.length() - 1);
  635. String objpath = obj.getCanonicalPath();
  636. File linefile = new File(line);
  637. String linepath = linefile.getCanonicalPath();
  638. //System.out.println(" isAlreadyCompiled: obj = " + objpath);
  639. //System.out.println(" isAlreadyCompiled: line = " + linepath);
  640. if (objpath.compareTo(linepath) == 0) {
  641. need_obj_parse = false;
  642. continue;
  643. } else {
  644. ret = false; // object named inside .d file is not the correct file!
  645. break;
  646. }
  647. } else {
  648. ret = false; // object file supposed to end with ':', but didn't
  649. break;
  650. }
  651. } else {
  652. // line is a prerequisite file
  653. File prereq = new File(line);
  654. if (!prereq.exists()) {
  655. ret = false; // prerequisite file did not exist
  656. break;
  657. }
  658. if (prereq.lastModified() >= obj_modified) {
  659. ret = false; // prerequisite modified since object was compiled
  660. break;
  661. }
  662. //System.out.println(" isAlreadyCompiled: prerequisite ok");
  663. }
  664. }
  665. } catch (Exception e) {
  666. return false; // any error reading dep file = recompile it
  667. } finally {
  668. IOUtils.closeQuietly(reader);
  669. }
  670. if (ret && verbose) {
  671. System.out.println(I18n.format(tr("Using previously compiled file: {0}"), obj.getPath()));
  672. }
  673. return ret;
  674. }
  675. /**
  676. * Either succeeds or throws a RunnerException fit for public consumption.
  677. */
  678. private void execAsynchronously(String[] command) throws RunnerException {
  679. // eliminate any empty array entries
  680. List<String> stringList = new ArrayList<String>();
  681. for (String string : command) {
  682. string = string.trim();
  683. if (string.length() != 0)
  684. stringList.add(string);
  685. }
  686. command = stringList.toArray(new String[stringList.size()]);
  687. if (command.length == 0)
  688. return;
  689. if (verbose) {
  690. for (String c : command)
  691. System.out.print(c + " ");
  692. System.out.println();
  693. }
  694. DefaultExecutor executor = new DefaultExecutor();
  695. executor.setStreamHandler(new PumpStreamHandler() {
  696. @Override
  697. protected Thread createPump(InputStream is, OutputStream os, boolean closeWhenExhausted) {
  698. final Thread result = new Thread(new MyStreamPumper(is, Compiler.this));
  699. result.setDaemon(true);
  700. return result;
  701. }
  702. });
  703. CommandLine commandLine = new DoubleQuotedArgumentsOnWindowsCommandLine(command[0]);
  704. for (int i = 1; i < command.length; i++) {
  705. commandLine.addArgument(command[i], false);
  706. }
  707. int result;
  708. executor.setExitValues(null);
  709. try {
  710. result = executor.execute(commandLine);
  711. } catch (IOException e) {
  712. RunnerException re = new RunnerException(e.getMessage());
  713. re.hideStackTrace();
  714. throw re;
  715. }
  716. executor.setExitValues(new int[0]);
  717. // an error was queued up by message(), barf this back to compile(),
  718. // which will barf it back to Editor. if you're having trouble
  719. // discerning the imagery, consider how cows regurgitate their food
  720. // to digest it, and the fact that they have five stomaches.
  721. //
  722. //System.out.println("throwing up " + exception);
  723. if (exception != null)
  724. throw exception;
  725. if (result > 1) {
  726. // a failure in the tool (e.g. unable to locate a sub-executable)
  727. System.err
  728. .println(I18n.format(tr("{0} returned {1}"), command[0], result));
  729. }
  730. if (result != 0) {
  731. RunnerException re = new RunnerException(tr("Error compiling."));
  732. re.hideStackTrace();
  733. throw re;
  734. }
  735. }
  736. /**
  737. * Part of the MessageConsumer interface, this is called
  738. * whenever a piece (usually a line) of error message is spewed
  739. * out from the compiler. The errors are parsed for their contents
  740. * and line number, which is then reported back to Editor.
  741. */
  742. public void message(String s) {
  743. int i;
  744. // remove the build path so people only see the filename
  745. // can't use replaceAll() because the path may have characters in it which
  746. // have meaning in a regular expression.
  747. if (!verbose) {
  748. String buildPath = prefs.get("build.path");
  749. while ((i = s.indexOf(buildPath + File.separator)) != -1) {
  750. s = s.substring(0, i) + s.substring(i + (buildPath + File.separator).length());
  751. }
  752. }
  753. // look for error line, which contains file name, line number,
  754. // and at least the first line of the error message
  755. String errorFormat = "(.+\\.\\w+):(\\d+)(:\\d+)*:\\s*error:\\s*(.*)\\s*";
  756. String[] pieces = PApplet.match(s, errorFormat);
  757. // if (pieces != null && exception == null) {
  758. // exception = sketch.placeException(pieces[3], pieces[1], PApplet.parseInt(pieces[2]) - 1);
  759. // if (exception != null) exception.hideStackTrace();
  760. // }
  761. if (pieces != null) {
  762. String error = pieces[pieces.length - 1], msg = "";
  763. if (error.trim().equals("SPI.h: No such file or directory")) {
  764. error = tr("Please import the SPI library from the Sketch > Import Library menu.");
  765. msg = tr("\nAs of Arduino 0019, the Ethernet library depends on the SPI library." +
  766. "\nYou appear to be using it or another library that depends on the SPI library.\n\n");
  767. }
  768. if (error.trim().equals("'BYTE' was not declared in this scope")) {
  769. error = tr("The 'BYTE' keyword is no longer supported.");
  770. msg = tr("\nAs of Arduino 1.0, the 'BYTE' keyword is no longer supported." +
  771. "\nPlease use Serial.write() instead.\n\n");
  772. }
  773. if (error.trim().equals("no matching function for call to 'Server::Server(int)'")) {
  774. error = tr("The Server class has been renamed EthernetServer.");
  775. msg = tr("\nAs of Arduino 1.0, the Server class in the Ethernet library " +
  776. "has been renamed to EthernetServer.\n\n");
  777. }
  778. if (error.trim().equals("no matching function for call to 'Client::Client(byte [4], int)'")) {
  779. error = tr("The Client class has been renamed EthernetClient.");
  780. msg = tr("\nAs of Arduino 1.0, the Client class in the Ethernet library " +
  781. "has been renamed to EthernetClient.\n\n");
  782. }
  783. if (error.trim().equals("'Udp' was not declared in this scope")) {
  784. error = tr("The Udp class has been renamed EthernetUdp.");
  785. msg = tr("\nAs of Arduino 1.0, the Udp class in the Ethernet library " +
  786. "has been renamed to EthernetUdp.\n\n");
  787. }
  788. if (error.trim().equals("'class TwoWire' has no member named 'send'")) {
  789. error = tr("Wire.send() has been renamed Wire.write().");
  790. msg = tr("\nAs of Arduino 1.0, the Wire.send() function was renamed " +
  791. "to Wire.write() for consistency with other libraries.\n\n");
  792. }
  793. if (error.trim().equals("'class TwoWire' has no member named 'receive'")) {
  794. error = tr("Wire.receive() has been renamed Wire.read().");
  795. msg = tr("\nAs of Arduino 1.0, the Wire.receive() function was renamed " +
  796. "to Wire.read() for consistency with other libraries.\n\n");
  797. }
  798. if (error.trim().equals("'Mouse' was not declared in this scope")) {
  799. error = tr("'Mouse' only supported on the Arduino Leonardo");
  800. //msg = _("\nThe 'Mouse' class is only supported on the Arduino Leonardo.\n\n");
  801. }
  802. if (error.trim().equals("'Keyboard' was not declared in this scope")) {
  803. error = tr("'Keyboard' only supported on the Arduino Leonardo");
  804. //msg = _("\nThe 'Keyboard' class is only supported on the Arduino Leonardo.\n\n");
  805. }
  806. RunnerException e = null;
  807. if (!sketchIsCompiled) {
  808. // Place errors when compiling the sketch, but never while compiling libraries
  809. // or the core. The user's sketch might contain the same filename!
  810. e = placeException(error, pieces[1], PApplet.parseInt(pieces[2]) - 1);
  811. }
  812. // replace full file path with the name of the sketch tab (unless we're
  813. // in verbose mode, in which case don't modify the compiler output)
  814. if (e != null && !verbose) {
  815. SketchCode code = sketch.getCode(e.getCodeIndex());
  816. String fileName = (code.isExtension("ino") || code.isExtension("pde")) ? code.getPrettyName() : code.getFileName();
  817. int lineNum = e.getCodeLine() + 1;
  818. s = fileName + ":" + lineNum + ": error: " + error + msg;
  819. }
  820. if (e != null) {
  821. if (exception == null || exception.getMessage().equals(e.getMessage())) {
  822. exception = e;
  823. exception.hideStackTrace();
  824. }
  825. }
  826. }
  827. if (s.contains("undefined reference to `SPIClass::begin()'") &&
  828. s.contains("libraries/Robot_Control")) {
  829. String error = tr("Please import the SPI library from the Sketch > Import Library menu.");
  830. exception = new RunnerException(error);
  831. }
  832. if (s.contains("undefined reference to `Wire'") &&
  833. s.contains("libraries/Robot_Control")) {
  834. String error = tr("Please import the Wire library from the Sketch > Import Library menu.");
  835. exception = new RunnerException(error);
  836. }
  837. System.err.print(s);
  838. }
  839. private String[] getCommandCompilerByRecipe(List<File> includeFolders, File sourceFile, File objectFile, String recipe) throws PreferencesMapException, RunnerException {
  840. String includes = prepareIncludes(includeFolders);
  841. PreferencesMap dict = new PreferencesMap(prefs);
  842. dict.put("ide_version", "" + BaseNoGui.REVISION);
  843. dict.put("includes", includes);
  844. dict.put("source_file", sourceFile.getAbsolutePath());
  845. dict.put("object_file", objectFile.getAbsolutePath());
  846. setupWarningFlags(dict);
  847. String cmd = prefs.getOrExcept(recipe);
  848. try {
  849. return StringReplacer.formatAndSplit(cmd, dict, true);
  850. } catch (Exception e) {
  851. throw new RunnerException(e);
  852. }
  853. }
  854. private void setupWarningFlags(PreferencesMap dict) {
  855. if (dict.containsKey("compiler.warning_level")) {
  856. String key = "compiler.warning_flags." + dict.get("compiler.warning_level");
  857. dict.put("compiler.warning_flags", dict.get(key));
  858. } else {
  859. dict.put("compiler.warning_flags", dict.get("compiler.warning_flags.none"));
  860. }
  861. if (dict.get("compiler.warning_flags") == null) {
  862. dict.remove("compiler.warning_flags");
  863. }
  864. }
  865. /////////////////////////////////////////////////////////////////////////////
  866. private void createFolder(File folder) throws RunnerException {
  867. if (folder.isDirectory())
  868. return;
  869. if (!folder.mkdir())
  870. throw new RunnerException("Couldn't create: " + folder);
  871. }
  872. static public List<File> findFilesInFolder(File folder, String extension,
  873. boolean recurse) {
  874. List<File> files = new ArrayList<File>();
  875. if (FileUtils.isSCCSOrHiddenFile(folder)) {
  876. return files;
  877. }
  878. File[] listFiles = folder.listFiles();
  879. if (listFiles == null) {
  880. return files;
  881. }
  882. for (File file : listFiles) {
  883. if (FileUtils.isSCCSOrHiddenFile(file)) {
  884. continue; // skip hidden files
  885. }
  886. if (file.getName().endsWith("." + extension))
  887. files.add(file);
  888. if (recurse && file.isDirectory()) {
  889. files.addAll(findFilesInFolder(file, extension, true));
  890. }
  891. }
  892. return files;
  893. }
  894. // 1. compile the sketch (already in the buildPath)
  895. void compileSketch(List<File> includeFolders, File buildPath) throws RunnerException, PreferencesMapException {
  896. objectFiles.addAll(compileFiles(buildPath, buildPath, false, includeFolders));
  897. }
  898. // 2. compile the libraries, outputting .o files to:
  899. // <buildPath>/<library>/
  900. void compileLibraries(List<File> includeFolders) throws RunnerException, PreferencesMapException {
  901. for (UserLibrary lib : importedLibraries) {
  902. compileLibrary(lib, includeFolders);
  903. }
  904. }
  905. private void compileLibrary(UserLibrary lib, List<File> includeFolders)
  906. throws RunnerException, PreferencesMapException {
  907. File libFolder = lib.getSrcFolder();
  908. File librariesFolder = new File(prefs.getFile("build.path"), "libraries");
  909. if (!librariesFolder.exists() && !librariesFolder.mkdirs()) {
  910. throw new RunnerException("Unable to create folder " + librariesFolder);
  911. }
  912. File libBuildFolder = new File(librariesFolder, lib.getName());
  913. if (lib.useRecursion()) {
  914. // libBuildFolder == {build.path}/LibName
  915. // libFolder == {lib.path}/src
  916. recursiveCompileFilesInFolder(libBuildFolder, libFolder, includeFolders);
  917. } else {
  918. // libFolder == {lib.path}/
  919. // utilityFolder == {lib.path}/utility
  920. // libBuildFolder == {build.path}/LibName
  921. // utilityBuildFolder == {build.path}/LibName/utility
  922. File utilityFolder = new File(libFolder, "utility");
  923. File utilityBuildFolder = new File(libBuildFolder, "utility");
  924. includeFolders.add(utilityFolder);
  925. compileFilesInFolder(libBuildFolder, libFolder, includeFolders);
  926. compileFilesInFolder(utilityBuildFolder, utilityFolder, includeFolders);
  927. // other libraries should not see this library's utility/ folder
  928. includeFolders.remove(utilityFolder);
  929. }
  930. }
  931. private void recursiveCompileFilesInFolder(File srcBuildFolder, File srcFolder, List<File> includeFolders) throws RunnerException, PreferencesMapException {
  932. compileFilesInFolder(srcBuildFolder, srcFolder, includeFolders);
  933. for (File subFolder : srcFolder.listFiles(new OnlyDirs())) {
  934. File subBuildFolder = new File(srcBuildFolder, subFolder.getName());
  935. recursiveCompileFilesInFolder(subBuildFolder, subFolder, includeFolders);
  936. }
  937. }
  938. private void compileFilesInFolder(File buildFolder, File srcFolder, List<File> includeFolders) throws RunnerException, PreferencesMapException {
  939. createFolder(buildFolder);
  940. List<File> objects = compileFiles(buildFolder, srcFolder, false, includeFolders);
  941. objectFiles.addAll(objects);
  942. }
  943. // 3. compile the core, outputting .o files to <buildPath> and then
  944. // collecting them into the core.a library file.
  945. // Also compiles the variant (if it supplies actual source files),
  946. // which are included in the link directly (not through core.a)
  947. void compileCore()
  948. throws RunnerException, PreferencesMapException {
  949. File coreFolder = prefs.getFile("build.core.path");
  950. File variantFolder = prefs.getFile("build.variant.path");
  951. File buildFolder = new File(prefs.getFile("build.path"), "core");
  952. if (!buildFolder.exists() && !buildFolder.mkdirs()) {
  953. throw new RunnerException("Unable to create folder " + buildFolder);
  954. }
  955. List<File> includeFolders = new ArrayList<File>();
  956. includeFolders.add(coreFolder); // include core path only
  957. if (variantFolder != null)
  958. includeFolders.add(variantFolder);
  959. if (variantFolder != null)
  960. objectFiles.addAll(compileFiles(buildFolder, variantFolder, true,
  961. includeFolders));
  962. File afile = new File(buildFolder, "core.a");
  963. List<File> coreObjectFiles = compileFiles(buildFolder, coreFolder, true,
  964. includeFolders);
  965. // See if the .a file is already uptodate
  966. if (afile.exists()) {
  967. boolean changed = false;
  968. for (File file : coreObjectFiles) {
  969. if (file.lastModified() > afile.lastModified()) {
  970. changed = true;
  971. break;
  972. }
  973. }
  974. // If none of the object files is newer than the .a file, don't
  975. // bother rebuilding the .a file. There is a small corner case
  976. // here: If a source file was removed, but no other source file
  977. // was modified, this will not rebuild core.a even when it
  978. // should. It's hard to fix and not a realistic case, so it
  979. // shouldn't be a problem.
  980. if (!changed) {
  981. if (verbose)
  982. System.out.println(I18n.format(tr("Using previously compiled file: {0}"), afile.getPath()));
  983. return;
  984. }
  985. }
  986. // Delete the .a file, to prevent any previous code from lingering
  987. afile.delete();
  988. try {
  989. for (File file : coreObjectFiles) {
  990. PreferencesMap dict = new PreferencesMap(prefs);
  991. dict.put("ide_version", "" + BaseNoGui.REVISION);
  992. dict.put("archive_file", afile.getName());
  993. dict.put("object_file", file.getAbsolutePath());
  994. dict.put("build.path", buildFolder.getAbsolutePath());
  995. String[] cmdArray;
  996. String cmd = prefs.getOrExcept("recipe.ar.pattern");
  997. try {
  998. cmdArray = StringReplacer.formatAndSplit(cmd, dict, true);
  999. } catch (Exception e) {
  1000. throw new RunnerException(e);
  1001. }
  1002. execAsynchronously(cmdArray);
  1003. }
  1004. } catch (RunnerException e) {
  1005. afile.delete();
  1006. throw e;
  1007. }
  1008. }
  1009. // 4. link it all together into the .elf file
  1010. void compileLink()
  1011. throws RunnerException, PreferencesMapException {
  1012. // TODO: Make the --relax thing in configuration files.
  1013. // For atmega2560, need --relax linker option to link larger
  1014. // programs correctly.
  1015. String optRelax = "";
  1016. if (prefs.get("build.mcu").equals("atmega2560"))
  1017. optRelax = ",--relax";
  1018. String objectFileList = "";
  1019. for (File file : objectFiles)
  1020. objectFileList += " \"" + file.getAbsolutePath() + '"';
  1021. objectFileList = objectFileList.substring(1);
  1022. PreferencesMap dict = new PreferencesMap(prefs);
  1023. String flags = dict.get("compiler.c.elf.flags") + optRelax;
  1024. dict.put("compiler.c.elf.flags", flags);
  1025. dict.put("archive_file", new File("core", "core.a").getPath());
  1026. dict.put("object_files", objectFileList);
  1027. dict.put("ide_version", "" + BaseNoGui.REVISION);
  1028. setupWarningFlags(dict);
  1029. String[] cmdArray;
  1030. String cmd = prefs.getOrExcept("recipe.c.combine.pattern");
  1031. try {
  1032. cmdArray = StringReplacer.formatAndSplit(cmd, dict, true);
  1033. } catch (Exception e) {
  1034. throw new RunnerException(e);
  1035. }
  1036. execAsynchronously(cmdArray);
  1037. }
  1038. void runActions(String recipeClass, PreferencesMap prefs) throws RunnerException, PreferencesMapException {
  1039. List<String> patterns = new ArrayList<String>();
  1040. for (String key : prefs.keySet()) {
  1041. if (key.startsWith("recipe."+recipeClass) && key.endsWith(".pattern"))
  1042. patterns.add(key);
  1043. }
  1044. Collections.sort(patterns);
  1045. for (String recipe : patterns) {
  1046. runRecipe(recipe);
  1047. }
  1048. }
  1049. void runRecipe(String recipe) throws RunnerException, PreferencesMapException {
  1050. PreferencesMap dict = new PreferencesMap(prefs);
  1051. dict.put("ide_version", "" + BaseNoGui.REVISION);
  1052. dict.put("sketch_path", sketch.getFolder().getAbsolutePath());
  1053. String[] cmdArray;
  1054. String cmd = prefs.getOrExcept(recipe);
  1055. try {
  1056. cmdArray = StringReplacer.formatAndSplit(cmd, dict, true);
  1057. } catch (Exception e) {
  1058. throw new RunnerException(e);
  1059. }
  1060. execAsynchronously(cmdArray);
  1061. }
  1062. private void mergeSketchWithBootloaderIfAppropriate(String className, PreferencesMap prefs) throws IOException {
  1063. if (!prefs.containsKey("bootloader.noblink") && !prefs.containsKey("bootloader.file")) {
  1064. return;
  1065. }
  1066. String buildPath = prefs.get("build.path");
  1067. Path sketch;
  1068. Path sketchInSubfolder = Paths.get(buildPath, "sketch", className + ".hex");
  1069. Path sketchInBuildPath = Paths.get(buildPath, className + ".hex");
  1070. if (Files.exists(sketchInSubfolder)) {
  1071. sketch = sketchInSubfolder;
  1072. } else if (Files.exists(sketchInBuildPath)) {
  1073. sketch = sketchInBuildPath;
  1074. } else {
  1075. return;
  1076. }
  1077. String bootloaderNoBlink = prefs.get("bootloader.noblink");
  1078. if (bootloaderNoBlink == null) {
  1079. bootloaderNoBlink = prefs.get("bootloader.file");
  1080. }
  1081. Path bootloader = Paths.get(prefs.get("runtime.platform.path"), "bootloaders", bootloaderNoBlink);
  1082. if (!Files.exists(bootloader)) {
  1083. System.err.println(I18n.format(tr("Bootloader file specified but missing: {0}"), bootloader));
  1084. return;
  1085. }
  1086. Path mergedSketch;
  1087. if ("sketch".equals(sketch.getParent().getFileName().toString())) {
  1088. mergedSketch = Paths.get(buildPath, "sketch", className + ".with_bootloader.hex");
  1089. } else {
  1090. mergedSketch = Paths.get(buildPath, className + ".with_bootloader.hex");
  1091. }
  1092. Files.copy(sketch, mergedSketch, StandardCopyOption.REPLACE_EXISTING);
  1093. new MergeSketchWithBooloader().merge(mergedSketch.toFile(), bootloader.toFile());
  1094. }
  1095. //7. Save the .hex file
  1096. void saveHex() throws RunnerException {
  1097. List<String> compiledSketches = new ArrayList<>(prefs.subTree("recipe.output.tmp_file", 1).values());
  1098. List<String> copyOfCompiledSketches = new ArrayList<>(prefs.subTree("recipe.output.save_file", 1).values());
  1099. if (isExportCompiledSketchSupported(compiledSketches, copyOfCompiledSketches)) {
  1100. System.err.println(tr("Warning: This core does not support exporting sketches. Please consider upgrading it or contacting its author"));
  1101. return;
  1102. }
  1103. PreferencesMap dict = new PreferencesMap(prefs);
  1104. dict.put("ide_version", "" + BaseNoGui.REVISION);
  1105. if (!compiledSketches.isEmpty()) {
  1106. for (int i = 0; i < compiledSketches.size(); i++) {
  1107. saveHex(compiledSketches.get(i), copyOfCompiledSketches.get(i), prefs);
  1108. }
  1109. } else {
  1110. try {
  1111. saveHex(prefs.getOrExcept("recipe.output.tmp_file"), prefs.getOrExcept("recipe.output.save_file"), prefs);
  1112. } catch (PreferencesMapException e) {
  1113. throw new RunnerException(e);
  1114. }
  1115. }
  1116. }
  1117. private boolean isExportCompiledSketchSupported(List<String> compiledSketches, List<String> copyOfCompiledSketches) {
  1118. return (compiledSketches.isEmpty() || copyOfCompiledSketches.isEmpty() || copyOfCompiledSketches.size() < compiledSketches.size()) && (!prefs.containsKey("recipe.output.tmp_file") || !prefs.containsKey("recipe.output.save_file"));
  1119. }
  1120. private void saveHex(String compiledSketch, String copyOfCompiledSketch, PreferencesMap dict) throws RunnerException {
  1121. try {
  1122. compiledSketch = StringReplacer.replaceFromMapping(compiled