diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 5f351f76477..fd5ab7df68a 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -1323,8 +1323,6 @@ public void onBoardOrPortChange() { } catch (IOException e) { showWarning(_("Error"), _("Error loading libraries"), e); } - String currentArch = Base.getTargetPlatform().getId(); - libraries = libraries.filterByArchitecture(currentArch); // Populate importToLibraryTable importToLibraryTable = new HashMap(); diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index c8a2967ec0f..5d66e18a89e 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -38,7 +38,6 @@ import processing.core.*; import static processing.app.I18n._; -import java.awt.*; import java.io.*; import java.util.*; import java.util.List; diff --git a/app/src/processing/app/debug/Compiler.java b/app/src/processing/app/debug/Compiler.java index 5bdb7d2e989..ae3f7e763cc 100644 --- a/app/src/processing/app/debug/Compiler.java +++ b/app/src/processing/app/debug/Compiler.java @@ -30,6 +30,7 @@ import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -88,30 +89,46 @@ public boolean compile(boolean _verbose) throws RunnerException { // 0. include paths for core + all libraries sketch.setCompilingProgress(20); - List includePaths = new ArrayList(); - includePaths.add(prefs.get("build.core.path")); - if (prefs.get("build.variant.path").length() != 0) - includePaths.add(prefs.get("build.variant.path")); + List includeFolders = new ArrayList(); + includeFolders.add(prefs.getFile("build.core.path")); + if (prefs.getFile("build.variant.path") != null) + includeFolders.add(prefs.getFile("build.variant.path")); for (Library lib : sketch.getImportedLibraries()) { if (verbose) System.out.println(I18n .format(_("Using library {0} in folder: {1} {2}"), lib.getName(), - lib.getFolder(), lib.isPre15Lib() ? "(pre-1.5)" : "")); - for (File folder : lib.getSrcFolders(targetArch)) - includePaths.add(folder.getPath()); + lib.getFolder(), lib.isLegacy() ? "(legacy)" : "")); + includeFolders.add(lib.getSrcFolder()); } if (verbose) System.out.println(); + + List archs = new ArrayList(); + archs.add(Base.getTargetPlatform().getId()); + if (prefs.containsKey("architecture.override_check")) { + String[] overrides = prefs.get("architecture.override_check").split(","); + archs.addAll(Arrays.asList(overrides)); + } + for (Library lib : sketch.getImportedLibraries()) { + if (!lib.supportsArchitecture(archs)) { + System.err.println(I18n + .format(_("WARNING: library {0} claims to run on {1} " + + "architecture(s) and may be incompatible with your" + + " current board which runs on {2} architecture(s)."), lib + .getName(), lib.getArchitectures(), archs)); + System.err.println(); + } + } // 1. compile the sketch (already in the buildPath) sketch.setCompilingProgress(30); - compileSketch(includePaths); + compileSketch(includeFolders); sketchIsCompiled = true; // 2. compile the libraries, outputting .o files to: // // Doesn't really use configPreferences sketch.setCompilingProgress(40); - compileLibraries(includePaths); + compileLibraries(includeFolders); // 3. compile the core, outputting .o files to and then // collecting them into the core.a library file. @@ -120,15 +137,15 @@ public boolean compile(boolean _verbose) throws RunnerException { // 4. link it all together into the .elf file sketch.setCompilingProgress(60); - compileLink(includePaths); + compileLink(); // 5. extract EEPROM data (from EEMEM directive) to .eep file. sketch.setCompilingProgress(70); - compileEep(includePaths); + compileEep(); // 6. build the .hex file sketch.setCompilingProgress(80); - compileHex(includePaths); + compileHex(); sketch.setCompilingProgress(90); return true; @@ -218,8 +235,8 @@ private PreferencesMap createBuildPreferences(String _buildPath, return p; } - private List compileFiles(String outputPath, File sourcePath, - boolean recurse, List includePaths) + private List compileFiles(File outputPath, File sourcePath, + boolean recurse, List includeFolders) throws RunnerException { List sSources = findFilesInFolder(sourcePath, "S", recurse); List cSources = findFilesInFolder(sourcePath, "c", recurse); @@ -227,36 +244,29 @@ private List compileFiles(String outputPath, File sourcePath, List objectPaths = new ArrayList(); for (File file : sSources) { - String objectPath = outputPath + File.separator + file.getName() + ".o"; - objectPaths.add(new File(objectPath)); - String[] cmd = getCommandCompilerS(includePaths, file.getAbsolutePath(), - objectPath); + File objectFile = new File(outputPath, file.getName() + ".o"); + objectPaths.add(objectFile); + String[] cmd = getCommandCompilerS(includeFolders, file, objectFile); execAsynchronously(cmd); } for (File file : cSources) { - String objectPath = outputPath + File.separator + file.getName() + ".o"; - String dependPath = outputPath + File.separator + file.getName() + ".d"; - File objectFile = new File(objectPath); - File dependFile = new File(dependPath); + File objectFile = new File(outputPath, file.getName() + ".o"); + File dependFile = new File(outputPath, file.getName() + ".d"); objectPaths.add(objectFile); if (isAlreadyCompiled(file, objectFile, dependFile, prefs)) continue; - String[] cmd = getCommandCompilerC(includePaths, file.getAbsolutePath(), - objectPath); + String[] cmd = getCommandCompilerC(includeFolders, file, objectFile); execAsynchronously(cmd); } for (File file : cppSources) { - String objectPath = outputPath + File.separator + file.getName() + ".o"; - String dependPath = outputPath + File.separator + file.getName() + ".d"; - File objectFile = new File(objectPath); - File dependFile = new File(dependPath); + File objectFile = new File(outputPath, file.getName() + ".o"); + File dependFile = new File(outputPath, file.getName() + ".d"); objectPaths.add(objectFile); if (isAlreadyCompiled(file, objectFile, dependFile, prefs)) continue; - String[] cmd = getCommandCompilerCPP(includePaths, - file.getAbsolutePath(), objectPath); + String[] cmd = getCommandCompilerCPP(includeFolders, file, objectFile); execAsynchronously(cmd); } @@ -511,15 +521,15 @@ public void message(String s) { System.err.print(s); } - private String[] getCommandCompilerS(List includePaths, - String sourceName, String objectName) + private String[] getCommandCompilerS(List includeFolders, + File sourceFile, File objectFile) throws RunnerException { - String includes = preparePaths(includePaths); + String includes = prepareIncludes(includeFolders); PreferencesMap dict = new PreferencesMap(prefs); dict.put("ide_version", "" + Base.REVISION); dict.put("includes", includes); - dict.put("source_file", sourceName); - dict.put("object_file", objectName); + dict.put("source_file", sourceFile.getAbsolutePath()); + dict.put("object_file", objectFile.getAbsolutePath()); try { String cmd = prefs.get("recipe.S.o.pattern"); @@ -529,16 +539,16 @@ private String[] getCommandCompilerS(List includePaths, } } - private String[] getCommandCompilerC(List includePaths, - String sourceName, String objectName) + private String[] getCommandCompilerC(List includeFolders, + File sourceFile, File objectFile) throws RunnerException { - String includes = preparePaths(includePaths); + String includes = prepareIncludes(includeFolders); PreferencesMap dict = new PreferencesMap(prefs); dict.put("ide_version", "" + Base.REVISION); dict.put("includes", includes); - dict.put("source_file", sourceName); - dict.put("object_file", objectName); + dict.put("source_file", sourceFile.getAbsolutePath()); + dict.put("object_file", objectFile.getAbsolutePath()); String cmd = prefs.get("recipe.c.o.pattern"); try { @@ -548,16 +558,16 @@ private String[] getCommandCompilerC(List includePaths, } } - private String[] getCommandCompilerCPP(List includePaths, - String sourceName, String objectName) + private String[] getCommandCompilerCPP(List includeFolders, + File sourceFile, File objectFile) throws RunnerException { - String includes = preparePaths(includePaths); + String includes = prepareIncludes(includeFolders); PreferencesMap dict = new PreferencesMap(prefs); dict.put("ide_version", "" + Base.REVISION); dict.put("includes", includes); - dict.put("source_file", sourceName); - dict.put("object_file", objectName); + dict.put("source_file", sourceFile.getAbsolutePath()); + dict.put("object_file", objectFile.getAbsolutePath()); String cmd = prefs.get("recipe.cpp.o.pattern"); try { @@ -606,56 +616,58 @@ static public List findFilesInFolder(File folder, String extension, } // 1. compile the sketch (already in the buildPath) - void compileSketch(List includePaths) throws RunnerException { - String buildPath = prefs.get("build.path"); - objectFiles.addAll(compileFiles(buildPath, new File(buildPath), false, - includePaths)); + void compileSketch(List includeFolders) throws RunnerException { + File buildPath = prefs.getFile("build.path"); + objectFiles.addAll(compileFiles(buildPath, buildPath, false, includeFolders)); } // 2. compile the libraries, outputting .o files to: // // - void compileLibraries(List includePaths) throws RunnerException { - File outputPath = new File(prefs.get("build.path")); + void compileLibraries(List includeFolders) throws RunnerException { for (Library lib : sketch.getImportedLibraries()) { - for (File folder : lib.getSrcFolders(targetArch)) { - if (lib.isPre15Lib()) { - compileLibrary(outputPath, folder, includePaths); - } else { - recursiveCompileLibrary(outputPath, folder, includePaths); - } - } + compileLibrary(lib, includeFolders); } } - private void recursiveCompileLibrary(File outputPath, File libraryFolder, List includePaths) throws RunnerException { - File newOutputPath = compileFilesInFolder(outputPath, libraryFolder, includePaths); - for (File subFolder : libraryFolder.listFiles(new OnlyDirs())) { - recursiveCompileLibrary(newOutputPath, subFolder, includePaths); + private void compileLibrary(Library lib, List includeFolders) + throws RunnerException { + File libFolder = lib.getSrcFolder(); + File libBuildFolder = prefs.getFile(("build.path"), lib.getName()); + + if (lib.useRecursion()) { + // libBuildFolder == {build.path}/LibName + // libFolder == {lib.path}/src + recursiveCompileFilesInFolder(libBuildFolder, libFolder, includeFolders); + + } else { + // libFolder == {lib.path}/ + // utilityFolder == {lib.path}/utility + // libBuildFolder == {build.path}/LibName + // utilityBuildFolder == {build.path}/LibName/utility + File utilityFolder = new File(libFolder, "utility"); + File utilityBuildFolder = new File(libBuildFolder, "utility"); + + includeFolders.add(utilityFolder); + compileFilesInFolder(libBuildFolder, libFolder, includeFolders); + compileFilesInFolder(utilityBuildFolder, utilityFolder, includeFolders); + + // other libraries should not see this library's utility/ folder + includeFolders.remove(utilityFolder); } } - private File compileFilesInFolder(File outputPath, File libraryFolder, List includePaths) throws RunnerException { - File outputFolder = new File(outputPath, libraryFolder.getName()); - createFolder(outputFolder); - objectFiles.addAll(compileFiles(outputFolder.getAbsolutePath(), libraryFolder, false, includePaths)); - return outputFolder; + private void recursiveCompileFilesInFolder(File srcBuildFolder, File srcFolder, List includeFolders) throws RunnerException { + compileFilesInFolder(srcBuildFolder, srcFolder, includeFolders); + for (File subFolder : srcFolder.listFiles(new OnlyDirs())) { + File subBuildFolder = new File(srcBuildFolder, subFolder.getName()); + recursiveCompileFilesInFolder(subBuildFolder, subFolder, includeFolders); + } } - private void compileLibrary(File outputPath, File libraryFolder, List includePaths) throws RunnerException { - File outputFolder = new File(outputPath, libraryFolder.getName()); - File utilityFolder = new File(libraryFolder, "utility"); - createFolder(outputFolder); - // this library can use includes in its utility/ folder - includePaths.add(utilityFolder.getAbsolutePath()); - - objectFiles.addAll(compileFiles(outputFolder.getAbsolutePath(), - libraryFolder, false, includePaths)); - outputFolder = new File(outputFolder, "utility"); - createFolder(outputFolder); - objectFiles.addAll(compileFiles(outputFolder.getAbsolutePath(), - utilityFolder, false, includePaths)); - // other libraries should not see this library's utility/ folder - includePaths.remove(includePaths.size() - 1); + private void compileFilesInFolder(File buildFolder, File srcFolder, List includeFolders) throws RunnerException { + createFolder(buildFolder); + List objects = compileFiles(buildFolder, srcFolder, false, includeFolders); + objectFiles.addAll(objects); } // 3. compile the core, outputting .o files to and then @@ -663,22 +675,22 @@ private void compileLibrary(File outputPath, File libraryFolder, List in void compileCore() throws RunnerException { - String corePath = prefs.get("build.core.path"); - String variantPath = prefs.get("build.variant.path"); - String buildPath = prefs.get("build.path"); + File coreFolder = prefs.getFile("build.core.path"); + File variantFolder = prefs.getFile("build.variant.path"); + File buildFolder = prefs.getFile("build.path"); - List includePaths = new ArrayList(); - includePaths.add(corePath); // include core path only - if (variantPath.length() != 0) - includePaths.add(variantPath); + List includeFolders = new ArrayList(); + includeFolders.add(coreFolder); // include core path only + if (variantFolder != null) + includeFolders.add(variantFolder); - List coreObjectFiles = compileFiles(buildPath, new File(corePath), - true, includePaths); - if (variantPath.length() != 0) - coreObjectFiles.addAll(compileFiles(buildPath, new File(variantPath), - true, includePaths)); + List objectFiles = compileFiles(buildFolder, coreFolder, true, + includeFolders); + if (variantFolder != null) + objectFiles.addAll(compileFiles(buildFolder, variantFolder, true, + includeFolders)); - for (File file : coreObjectFiles) { + for (File file : objectFiles) { PreferencesMap dict = new PreferencesMap(prefs); dict.put("ide_version", "" + Base.REVISION); @@ -697,7 +709,7 @@ void compileCore() } // 4. link it all together into the .elf file - void compileLink(List includePaths) + void compileLink() throws RunnerException { // TODO: Make the --relax thing in configuration files. @@ -731,7 +743,7 @@ void compileLink(List includePaths) } // 5. extract EEPROM data (from EEMEM directive) to .eep file. - void compileEep(List includePaths) throws RunnerException { + void compileEep() throws RunnerException { PreferencesMap dict = new PreferencesMap(prefs); dict.put("ide_version", "" + Base.REVISION); @@ -746,7 +758,7 @@ void compileEep(List includePaths) throws RunnerException { } // 6. build the .hex file - void compileHex(List includePaths) throws RunnerException { + void compileHex() throws RunnerException { PreferencesMap dict = new PreferencesMap(prefs); dict.put("ide_version", "" + Base.REVISION); @@ -760,10 +772,10 @@ void compileHex(List includePaths) throws RunnerException { execAsynchronously(cmdArray); } - private static String preparePaths(List includePaths) { + private static String prepareIncludes(List includeFolders) { String res = ""; - for (String p : includePaths) - res += " \"-I" + p + '"'; + for (File p : includeFolders) + res += " \"-I" + p.getAbsolutePath() + '"'; // Remove first space return res.substring(1); diff --git a/app/src/processing/app/helpers/PreferencesMap.java b/app/src/processing/app/helpers/PreferencesMap.java index cd3bb7dd2f7..bac942a7729 100644 --- a/app/src/processing/app/helpers/PreferencesMap.java +++ b/app/src/processing/app/helpers/PreferencesMap.java @@ -265,4 +265,37 @@ public String getOrExcept(String k) throws PreferencesMapException { public String toString() { return toString(""); } + + /** + * Creates a new File instance by converting the value of the key into an + * abstract pathname. If the the given key doesn't exists or his value is the + * empty string, the result is null. + * + * @param key + * @return + */ + public File getFile(String key) { + if (!containsKey(key)) + return null; + String path = get(key).trim(); + if (path.length() == 0) + return null; + return new File(path); + } + + /** + * Creates a new File instance by converting the value of the key into an + * abstract pathname with the specified sub folder. If the the given key + * doesn't exists or his value is the empty string, the result is null. + * + * @param key + * @param subFolder + * @return + */ + public File getFile(String key, String subFolder) { + File file = getFile(key); + if (file == null) + return null; + return new File(file, subFolder); + } } diff --git a/app/src/processing/app/packages/Library.java b/app/src/processing/app/packages/Library.java index a6322ab449f..2ac8e5a4379 100644 --- a/app/src/processing/app/packages/Library.java +++ b/app/src/processing/app/packages/Library.java @@ -1,7 +1,5 @@ package processing.app.packages; -import static processing.app.helpers.StringUtils.wildcardMatch; - import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -17,24 +15,30 @@ public class Library { private String name; private String version; private String author; - private String email; - private String url; + private String maintainer; private String sentence; private String paragraph; - private List coreDependencies; - private List dependencies; - private File folder, srcFolder, archFolder; + private String url; + private String category; + private String license; private List architectures; - private boolean pre15Lib; + private File folder; + private File srcFolder; + private boolean useRecursion; + private boolean isLegacy; private static final List MANDATORY_PROPERTIES = Arrays - .asList(new String[] { "architectures", "author", "core-dependencies", - "dependencies", "email", "name", "paragraph", "sentence", "url", - "version" }); + .asList(new String[] { "name", "version", "author", "maintainer", + "sentence", "paragraph", "url" }); + + private static final List CATEGORIES = Arrays.asList(new String[] { + "Display", "Communication", "Signal Input/Output", "Sensors", + "Device Control", "Timing", "Data Storage", "Data Processing", "Other", + "Uncategorized" }); /** * Scans inside a folder and create a Library object out of it. Automatically - * detects pre-1.5 libraries. Automatically fills metadata from + * detects legacy libraries. Automatically fills metadata from * library.properties file if found. * * @param libFolder @@ -45,7 +49,7 @@ static public Library create(File libFolder) throws IOException { // "library.properties" File check = new File(libFolder, "library.properties"); if (!check.exists() || !check.isFile()) - return createPre15Library(libFolder); + return createLegacyLibrary(libFolder); else return createLibrary(libFolder); } @@ -59,17 +63,43 @@ private static Library createLibrary(File libFolder) throws IOException { // Library sanity checks // --------------------- - // 1. Check mandatory properties + // Compatibility with 1.5 rev.1 libraries: + // "email" field changed to "maintainer" + if (!properties.containsKey("maintainer")) + properties.put("maintainer", properties.get("email")); + + // Compatibility with 1.5 rev.1 libraries: + // "arch" folder no longer supported + File archFolder = new File(libFolder, "arch"); + if (archFolder.isDirectory()) + throw new IOException("'arch' folder is no longer supported! See " + + "http://goo.gl/gfFJzU for more information"); + + // Check mandatory properties for (String p : MANDATORY_PROPERTIES) if (!properties.containsKey(p)) throw new IOException("Missing '" + p + "' from library"); - // 2. Check mandatory folders + // Check layout + boolean useRecursion; File srcFolder = new File(libFolder, "src"); - if (!srcFolder.exists() || !srcFolder.isDirectory()) - throw new IOException("Missing 'src' folder"); - // 3. Warn if root folder contains development leftovers + if (srcFolder.exists() && srcFolder.isDirectory()) { + // Layout with a single "src" folder and recursive compilation + useRecursion = true; + + File utilFolder = new File(libFolder, "utility"); + if (utilFolder.exists() && utilFolder.isDirectory()) { + throw new IOException( + "Library can't use both 'src' and 'utility' folders."); + } + } else { + // Layout with source code on library's root and "utility" folders + srcFolder = libFolder; + useRecursion = false; + } + + // Warn if root folder contains development leftovers for (File file : libFolder.listFiles()) { if (file.isDirectory()) { if (FileUtils.isSCCSOrHiddenFile(file)) { @@ -81,71 +111,77 @@ private static Library createLibrary(File libFolder) throws IOException { } // Extract metadata info + String architectures = properties.get("architectures"); + if (architectures == null) + architectures = "*"; // defaults to "any" List archs = new ArrayList(); - for (String arch : properties.get("architectures").split(",")) + for (String arch : architectures.split(",")) archs.add(arch.trim()); - List coreDeps = new ArrayList(); - for (String dep : properties.get("core-dependencies").split(",")) - coreDeps.add(dep.trim()); + String category = properties.get("category"); + if (category == null) + category = "Uncategorized"; + if (!CATEGORIES.contains(category)) + category = "Uncategorized"; - List dependencies = new ArrayList(); - for (String dependency : properties.get("dependencies").split(",")) { - dependency = dependency.trim(); - if (!dependency.equals("")) { - dependencies.add(dependency); - } - } + String license = properties.get("license"); + if (license == null) + license = "Unspecified"; Library res = new Library(); res.folder = libFolder; res.srcFolder = srcFolder; - res.archFolder = new File(libFolder, "arch"); res.name = properties.get("name").trim(); + res.version = properties.get("version").trim(); res.author = properties.get("author").trim(); - res.email = properties.get("email").trim(); + res.maintainer = properties.get("maintainer").trim(); res.sentence = properties.get("sentence").trim(); res.paragraph = properties.get("paragraph").trim(); res.url = properties.get("url").trim(); + res.category = category.trim(); + res.license = license.trim(); res.architectures = archs; - res.coreDependencies = coreDeps; - res.dependencies = dependencies; - res.version = properties.get("version").trim(); - res.pre15Lib = false; + res.useRecursion = useRecursion; + res.isLegacy = false; return res; } - private static Library createPre15Library(File libFolder) { + private static Library createLegacyLibrary(File libFolder) { // construct an old style library Library res = new Library(); res.folder = libFolder; res.srcFolder = libFolder; + res.useRecursion = false; res.name = libFolder.getName(); res.architectures = Arrays.asList("*"); - res.pre15Lib = true; + res.isLegacy = true; return res; } - public List getSrcFolders(String reqArch) { - if (!supportsArchitecture(reqArch)) - return null; - List res = new ArrayList(); - res.add(srcFolder); - File archSpecificFolder = new File(archFolder, reqArch); - if (archSpecificFolder.exists() && archSpecificFolder.isDirectory()) { - res.add(archSpecificFolder); - } else { - // If specific architecture folder is not found try with "default" - archSpecificFolder = new File(archFolder, "default"); - if (archSpecificFolder.exists() && archSpecificFolder.isDirectory()) - res.add(archSpecificFolder); - } - return res; + /** + * Returns true if the library declares to support the specified + * architecture (through the "architectures" property field). + * + * @param reqArch + * @return + */ + public boolean supportsArchitecture(String reqArch) { + return architectures.contains(reqArch) || architectures.contains("*"); } - public boolean supportsArchitecture(String reqArch) { - for (String arch : architectures) - if (wildcardMatch(reqArch, arch)) + /** + * Returns true if the library declares to support at least one of the + * specified architectures. + * + * @param reqArchs + * A List of architectures to check + * @return + */ + public boolean supportsArchitecture(List reqArchs) { + if (reqArchs.contains("*")) + return true; + for (String reqArch : reqArchs) + if (supportsArchitecture(reqArch)) return true; return false; } @@ -157,18 +193,10 @@ public int compare(Library o1, Library o2) { } }; - public File getSrcFolder() { - return srcFolder; - } - public String getName() { return name; } - public boolean isPre15Lib() { - return pre15Lib; - } - public File getFolder() { return folder; } @@ -181,18 +209,6 @@ public String getAuthor() { return author; } - public List getCoreDependencies() { - return coreDependencies; - } - - public List getDependencies() { - return dependencies; - } - - public String getEmail() { - return email; - } - public String getParagraph() { return paragraph; } @@ -205,23 +221,49 @@ public String getUrl() { return url; } + public String getCategory() { + return category; + } + + public String getLicense() { + return license; + } + + public static List getCategories() { + return CATEGORIES; + } + public String getVersion() { return version; } + public String getMaintainer() { + return maintainer; + } + + public boolean useRecursion() { + return useRecursion; + } + + public File getSrcFolder() { + return srcFolder; + } + + public boolean isLegacy() { + return isLegacy; + } + @Override public String toString() { String res = "Library:"; res += " (name=" + name + ")"; - res += " (architectures=" + architectures + ")"; + res += " (version=" + version + ")"; res += " (author=" + author + ")"; - res += " (core-dependencies=" + coreDependencies + ")"; - res += " (dependencies=" + dependencies + ")"; - res += " (email=" + email + ")"; - res += " (paragraph=" + paragraph + ")"; + res += " (maintainer=" + maintainer + ")"; res += " (sentence=" + sentence + ")"; + res += " (paragraph=" + paragraph + ")"; res += " (url=" + url + ")"; - res += " (version=" + version + ")"; + res += " (architectures=" + architectures + ")"; return res; } }