package io.sloeber.core.tools;
/** this package makes the .ino.cpp file.
* the .ino.cpp file includes all include directives and definitions in all the ino and pde files
* it also includes a include statement for all the ino and pde files themelves
* This way compiling the ino.cpp file compiles all ino and pde files in 1 file with declarations on top just like arduino ide does
*
* the custom managed build system delivered with the plugin ignores the ino and pde files
* this way the ino and pde files are only build once
*
* because I do not touch the ino and pde files the references returned by the toolchain
* are still perfectly valid removing the need for post processing
*
* Arduino ide ignores files starting with a . making the solution 100% compatible between arduino IDE and eclipse
*
* in standard configuration eclipse does not show the .ino.cpp file in the project explorer making the solution nice and clean from a visual perspective.
*
* I'm currently aware of 1 drawbacks of this solution
* If you have a file called .ino.cpp already in your project that file will be overwritten.
*/
import java.io.ByteArrayInputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTLinkageSpecification;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IInclude;
import org.eclipse.cdt.core.model.IMacro;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTFunctionDefinition;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
@SuppressWarnings({ "nls", "restriction" })
public class PdePreprocessor {
private static String tempFile = ".ino.cpp";
private static final String DEFINE_IN_ECLIPSE = "__IN_ECLIPSE__";
public static void processProject(IProject iProject) throws CoreException {
// first write some standard bla bla
final String NEWLINE = "\n";
String body = new String();
String includeHeaderPart = "#include \"Arduino.h\"" + NEWLINE;
String includeCodePart = NEWLINE;
String header = "//This is a automatic generated file" + NEWLINE;
header += "//Please do not modify this file" + NEWLINE;
header += "//If you touch this file your change will be overwritten during the next build" + NEWLINE;
header += "//This file has been generated on ";
header += new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
header += NEWLINE;
header += NEWLINE;
ICProject tt = CoreModel.getDefault().create(iProject);
IIndex index = CCorePlugin.getIndexManager().getIndex(tt);
try {
try {
index.acquireReadLock();
} catch (InterruptedException e) {
// ignore
e.printStackTrace();
return;
}
// take all the files in the project
IResource allResources[] = iProject.members(0);
int numInoFiles = 0;
for (IResource curResource : allResources) {
String extension = curResource.getFileExtension();
// only process .pde and .ino files
if (extension != null && ((extension.equals("pde") || extension.equals("ino")))) {
numInoFiles++;
String addLine;
if (curResource.isLinked()) {
addLine = "#include \"" + curResource.getLocation() + "\"" + NEWLINE;
} else {
addLine = "#include \"" + curResource.getName() + "\"" + NEWLINE;
}
// if the name of the ino/pde file matches the project put
// the file in front
// Otherwise add it to the end
if (curResource.getName().equals(iProject.getName() + "." + extension)) {
includeCodePart = addLine + includeCodePart;
} else {
includeCodePart += addLine;
}
IPath path = curResource.getFullPath();
// check whether the indexer is properly configured.
IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
ITranslationUnit tu = (ITranslationUnit) CoreModel.getDefault().create(file);
if (tu == null) {
// the indexer is not properly configured so drop a
// error in the file
body += NEWLINE;
body += "#error the file: " + curResource.getName()
+ " is not found in the indexer though it exists on the file system." + NEWLINE;
body += "#error this is probably due to a bad eclipse configuration : ino and pde are not marked as c++ file."
+ NEWLINE;
body += "#error please check whether *.ino and *.pde are marked as C++ source code in windows->preferences->C/C++->file types."
+ NEWLINE;
} else {
// add declarations made in ino files.
IASTTranslationUnit asttu = tu.getAST(index,
ITranslationUnit.AST_SKIP_FUNCTION_BODIES | ITranslationUnit.AST_SKIP_ALL_HEADERS);
IASTNode astNodes[] = asttu.getChildren();
for (IASTNode astNode : astNodes) {
if (astNode instanceof CPPASTFunctionDefinition) {
String addString = astNode.getRawSignature();
addString = addString.replaceAll("\r\n", NEWLINE);
addString = addString.replaceAll("\r", NEWLINE);
addString = addString.replaceAll("//[^\n]+\n", " ");
addString = addString.replaceAll("\n", " ");
addString = addString.replaceAll("\\{.*\\}", "");
if (addString.contains("=") || addString.contains("::")) {
// ignore when there are assignments in the
// declaration
// or when it is a class function
} else {
body += addString + ';' + NEWLINE;
}
}
}
// Locate All lines that are extern "C"
HashMap<Integer, Integer> externCLines = new HashMap<>();
IASTTranslationUnit astTuTest = tu.getAST(index, 0);
IASTDeclaration[] topDeclaratons = astTuTest.getDeclarations();
for (IASTDeclaration curTopDeclaration : topDeclaratons) {
ICPPASTLinkageSpecification test = curTopDeclaration instanceof ICPPASTLinkageSpecification
? (ICPPASTLinkageSpecification) curTopDeclaration : null;
if (test != null) {
if (test.getLiteral().equals("\"C\"")) {
Path curFile = new Path(curTopDeclaration.getContainingFilename());
if (curFile.equals(file.getLocation())) {
int startLine = test.getFileLocation().getStartingLineNumber();
int endLine = test.getFileLocation().getEndingLineNumber();
for (int curline = startLine; curline <= endLine; curline++) {
externCLines.put(new Integer(curline), null);
}
}
}
}
}
// find all the macro's
List<ICElement> theMacros = tu.getChildrenOfType(ICElement.C_MACRO);
// list all includes found in the source files.
IInclude includes[] = tu.getIncludes();
int prefHeaderLine = 0;
for (IInclude include : includes) {
int curHeaderLine = include.getSourceRange().getStartLine();
for (ICElement curElement : theMacros) {
IMacro curMacro = (IMacro) curElement;
int curMacroLine = curMacro.getSourceRange().getStartLine();
if ((curMacroLine < curHeaderLine) && (prefHeaderLine < curMacroLine)) {
includeHeaderPart += curMacro.getSource() + NEWLINE;
}
}
prefHeaderLine = curHeaderLine;
if (externCLines.containsKey(new Integer(curHeaderLine))) {
includeHeaderPart += "extern \"C\" {" + NEWLINE;
includeHeaderPart += include.getSource() + NEWLINE;
includeHeaderPart += "}" + NEWLINE;
} else {
includeHeaderPart += include.getSource();
includeHeaderPart += NEWLINE;
}
}
}
}
}
body = body + NEWLINE;
// delete the generated .ino.cpp file this is to cope with people
// renaming the ino files to cpp files removing the need for
// .ino.cpp file
if (numInoFiles == 0) {
IResource inofile = iProject.findMember(tempFile);
if (inofile != null) {
inofile.delete(true, null);
}
} else {
// concatenate the parts and make the .ino.cpp file
String output = header + includeHeaderPart + body + includeCodePart;
// Make sure the file is not procesed by Arduino IDE
output = "#ifdef " + DEFINE_IN_ECLIPSE + NEWLINE + output + NEWLINE + "#endif" + NEWLINE;
Helpers.addFileToProject(iProject, new Path(tempFile), new ByteArrayInputStream(output.getBytes()),
null, true);
}
} finally {
index.releaseReadLock();
}
}
}