/*******************************************************************************
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Synopsys, Inc. - ARC GNU Toolchain support
*******************************************************************************/
package com.arc.cdt.toolchain.tcf;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.cdt.cross.arc.gnu.ARCPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.ui.statushandlers.StatusManager;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.arc.cdt.toolchain.ArcCpu;
import com.arc.cdt.toolchain.ArcCpuFamily;
public class TcfContent {
public static final String GCC_OPTIONS_SECTION = "gcc_compiler";
public static final String LINKER_MEMORY_MAP_SECTION = "gnu_linker_command_file";
private Properties gccOptions;
private String linkerMemoryMap;
private long modTime;
private static Map<File, TcfContent> cache = new HashMap<File, TcfContent>();
private TcfContent() {
}
private static void checkNameNotEmpty(File f) throws FileNotFoundException {
if (f.getAbsolutePath().isEmpty()) {
throw new FileNotFoundException("TCF path's value must not be empty.");
}
}
private static void checkFileExists(File f) throws FileNotFoundException {
if (!f.exists()) {
throw new FileNotFoundException(
"File " + f.getAbsolutePath() + " does not exist in the system.");
}
}
/**
* Check that TCF file and used tool chain are for the same processor
* @param cpuFlag processor value from tool chain in "-mcpu=" form
* @throws TcfContentException
*/
private void checkArchitecture(String cpuFlag)
throws TcfContentException {
String tcfCpuOption = "-mcpu";
String value = gccOptions.getProperty(tcfCpuOption);
if (value.isEmpty()) {
throw new TcfContentException("Invalid option in TCF: " + tcfCpuOption + ".");
}
tcfCpuOption = tcfCpuOption + "=" + value;
ArcCpuFamily expectedArch = ArcCpu.fromCommand(cpuFlag).getToolChain();
ArcCpuFamily tcfArch = ArcCpu.fromCommand(tcfCpuOption).getToolChain();
if (!expectedArch.equals(tcfArch)) {
throw new TcfContentException("TCF describes " + tcfArch
+ " architecture, but selected tool chain is for " + expectedArch + ".");
}
}
/**
* Checks that file exists, then reads it and checks that TCF and used tool chain are for the
* same processor. If cannot read file or some of the checks fail, shows or logs the error or
* does nothing depending on the value of <code>showStyle</code>.
*
* @param f
* TCF to read
* @param cpuFlag
* processor value from tool chain in "-mcpu=" form
* @param showStyle
* style indicating what should be done in case exception occurred while reading.
* Applicable values are <code>StatusManager.NONE</code>,
* <code>StatusManager.LOG</code>, <code>StatusManager.SHOW</code> and
* <code>StatusManager.BLOCK</code>.
* @param messages
* Additional messages to display
* @return TcfContent, if reading was successful, and null otherwise
*/
public static TcfContent readFile(File f, String cpuFlag, int showStyle, String... messages) {
TcfContent tcfContent = null;
try {
tcfContent = readFile(f, cpuFlag);
} catch (TcfContentException e) {
StringBuilder builder = new StringBuilder(e.getMessage());
for (String message: messages) {
builder.append(message);
}
String message = builder.toString();
StatusManager.getManager().handle(new Status(IStatus.ERROR, ARCPlugin.PLUGIN_ID, message), showStyle);
}
return tcfContent;
}
/**
* Checks that file exists, then reads it and checks that TCF and used tool chain are for the
* same processor. If cannot read file or some of the checks fail, throws
* <code>TcfContentException</code>.
*
* @param f
* TCF to read
* @param cpuFlag
* processor value from tool chain in "-mcpu=" form
* @return TcfContent or null if cannot read
* @throws TcfContentException
*/
public static TcfContent readFile(File f, String cpuFlag) throws TcfContentException {
TcfContent tcfContent = cache.get(f);
if (tcfContent != null && tcfContent.modTime == f.lastModified()) {
tcfContent.checkArchitecture(cpuFlag);
return tcfContent;
}
try {
checkNameNotEmpty(f);
checkFileExists(f);
tcfContent = new TcfContent();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(f);
Element root = document.getDocumentElement();
NodeList attributes = root.getElementsByTagName("configuration");
for (int index = 0; index < attributes.getLength(); index++) {
Element e = (Element) attributes.item(index);
String elementName = e.getAttribute("name");
if (!elementName.equals(GCC_OPTIONS_SECTION)
&& !elementName.equals(LINKER_MEMORY_MAP_SECTION)) {
continue;
}
NodeList stringList = e.getElementsByTagName("string");
if (stringList == null || stringList.item(0) == null) {
throw new TcfContentException(
"Malformed TCF: No 'string' element in "
+ e.getAttribute("name") + " configuration");
}
String data = getCharacterDataFromElement((Element) stringList.item(0));
if (elementName.equals(GCC_OPTIONS_SECTION)) {
/*
* Should use OrderedProperties instead of Properties here because if several
* values for the same option are specified, the last one should be used. The
* order of options is important not only if several values of the same options
* are set in TCF explicitly, but also when options values are set implicitly by
* -mcpu option.
*/
tcfContent.gccOptions = new OrderedProperties();
/*
* Need to escape whitespaces here because in java.util.Properties key termination
* characters are '=', ':' and whitespace. So if our TCF has several option like
* "--param ...", --param will be considered a key and therefore Properties will
* load only one of these options. If we escape a whitespace, it will be considered
* part of a key.
*/
data = data.replace(" ", "\\ ");
tcfContent.gccOptions.load(new StringReader(data));
tcfContent.checkArchitecture(cpuFlag);
}
if (elementName.equals(LINKER_MEMORY_MAP_SECTION)) {
tcfContent.linkerMemoryMap = data;
}
}
} catch (SAXException | IOException | ParserConfigurationException e) {
throw new TcfContentException("Couldn't read TCF: " + e.getMessage(), e);
}
if (tcfContent.gccOptions == null || tcfContent.linkerMemoryMap == null) {
String sectionName = tcfContent.getGccOptions() == null ? GCC_OPTIONS_SECTION
: LINKER_MEMORY_MAP_SECTION;
throw new TcfContentException(
"Malformed TCF: " + sectionName + " configuration is missing");
}
tcfContent.modTime = f.lastModified();
cache.put(f, tcfContent);
return tcfContent;
}
private static String getCharacterDataFromElement(Element e) throws TcfContentException {
Node child = e.getFirstChild();
if (child == null) {
throw new TcfContentException(
"Malformed TCF: Couldn't get character data from element " + e.getNodeName());
}
if (child instanceof CharacterData) {
CharacterData data = (CharacterData) child;
return data.getData();
}
throw new TcfContentException(
"Malformed TCF: Couldn't get character data from element " + e.getNodeName());
}
public Properties getGccOptions() {
return gccOptions;
}
public String getLinkerMemoryMap() {
return linkerMemoryMap;
}
}