org.fisco.solc.compiler.SolidityCompiler Maven / Gradle / Ivy
The newest version!
package org.fisco.solc.compiler;
import static java.util.stream.Collectors.toList;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SolidityCompiler {
private static final Logger logger = LoggerFactory.getLogger(SolidityCompiler.class);
/** singleton object */
private static SolidityCompiler INSTANCE;
/** ecdsa compiler */
private Map solcMap = new ConcurrentHashMap<>();
/** sm compiler */
private Map sMSolcMap = new ConcurrentHashMap<>();
private void initSolc(boolean sm, Version version) {
if (sm && (!sMSolcMap.containsKey(version))) {
Solc sMSolc = new Solc(true, version);
sMSolcMap.put(version, sMSolc);
} else if (!sm && (!solcMap.containsKey(version))) {
Solc solc = new Solc(false, version);
solcMap.put(version, solc);
}
}
/**
* @param source
* @param sm
* @param combinedJson
* @param options
* @return
* @throws IOException
*/
public static Result compile(
File source, boolean sm, boolean combinedJson, Version version, Option... options)
throws IOException {
return getInstance().compileSrc(source, sm, false, combinedJson, version, options);
}
/**
* @param source
* @param sm
* @param combinedJson
* @param options
* @return
* @throws IOException
*/
public static Result compile(
byte[] source, boolean sm, boolean combinedJson, Version version, Option... options)
throws IOException {
return getInstance().compileSrc(source, sm, false, combinedJson, version, options);
}
/**
* This class is mainly here for backwards compatibility; however we are now reusing it making
* it the solely public interface listing all the supported options.
*/
public static final class Options {
public static final OutputOption AST = OutputOption.AST;
public static final OutputOption BIN = OutputOption.BIN;
public static final OutputOption ABI = OutputOption.ABI;
public static final OutputOption METADATA = OutputOption.METADATA;
public static final OutputOption ASTJSON = OutputOption.ASTJSON;
public static final OutputOption USERDOC = OutputOption.USERDOC;
public static final OutputOption DEVDOC = OutputOption.DEVDOC;
private static final NameOnlyOption OPTIMIZE = NameOnlyOption.OPTIMIZE;
private static final NameOnlyOption VERSION = NameOnlyOption.VERSION;
private static class CombinedJson extends ListOption {
private CombinedJson(List values) {
super("combined-json", values);
}
}
public static class AllowPaths extends ListOption {
public AllowPaths(List values) {
super("allow-paths", values);
}
}
}
public interface Option extends Serializable {
String getValue();
String getName();
}
private static class ListOption implements Option {
private String name;
private List values;
private ListOption(String name, List values) {
this.name = name;
this.values = values;
}
@Override
public String getValue() {
StringBuilder result = new StringBuilder();
for (Object value : values) {
if (OutputOption.class.isAssignableFrom(value.getClass())) {
result.append(
(result.length() == 0)
? ((OutputOption) value).getName()
: ',' + ((OutputOption) value).getName());
} else if (Path.class.isAssignableFrom(value.getClass())) {
result.append(
(result.length() == 0)
? ((Path) value).toAbsolutePath().toString()
: ',' + ((Path) value).toAbsolutePath().toString());
} else if (File.class.isAssignableFrom(value.getClass())) {
result.append(
(result.length() == 0)
? ((File) value).getAbsolutePath()
: ',' + ((File) value).getAbsolutePath());
} else if (String.class.isAssignableFrom(value.getClass())) {
result.append((result.length() == 0) ? value : "," + value);
} else {
throw new UnsupportedOperationException(
"Unexpected type, value '" + value + "' cannot be retrieved.");
}
}
return result.toString();
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}
private enum NameOnlyOption implements Option {
OPTIMIZE("optimize"),
VERSION("version");
private String name;
NameOnlyOption(String name) {
this.name = name;
}
@Override
public String getValue() {
return "";
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}
private enum OutputOption implements Option {
AST("ast"),
BIN("bin"),
ABI("abi"),
METADATA("metadata"),
ASTJSON("ast-json"),
USERDOC("userdoc"),
DEVDOC("devdoc");
private String name;
OutputOption(String name) {
this.name = name;
}
@Override
public String getValue() {
return "";
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}
public static class CustomOption implements Option {
private String name;
private String value;
public CustomOption(String name) {
if (name.startsWith("--")) {
this.name = name.substring(2);
} else {
this.name = name;
}
}
public CustomOption(String name, String value) {
this(name);
this.value = value;
}
@Override
public String getValue() {
return value;
}
@Override
public String getName() {
return name;
}
}
public static class Result {
private String errors;
private String output;
private boolean success;
public Result(String errors, String output, boolean success) {
this.errors = errors;
this.output = output;
this.success = success;
}
public boolean isFailed() {
return !success;
}
public String getErrors() {
return errors;
}
public void setErrors(String errors) {
this.errors = errors;
}
public String getOutput() {
return output;
}
public void setOutput(String output) {
this.output = output;
}
}
private static class ParallelReader extends Thread {
private InputStream stream;
private StringBuilder content = new StringBuilder();
ParallelReader(InputStream stream) {
this.stream = stream;
}
public String getContent() {
return getContent(true);
}
public synchronized String getContent(boolean waitForComplete) {
if (waitForComplete) {
while (stream != null) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}
return content.toString();
}
public void run() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
synchronized (this) {
stream = null;
notifyAll();
}
}
}
}
private Result compileSrc(
File source,
boolean sm,
boolean optimize,
boolean combinedJson,
Version version,
Option... options)
throws IOException {
if (logger.isDebugEnabled()) {
logger.debug(" source: {}, sm: {}", source.getAbsolutePath(), sm);
}
Solc tmpSolc = getSolc(sm, version);
List commandParts = prepareCommandOptions(tmpSolc, optimize, combinedJson, options);
commandParts.add(source.getAbsolutePath());
ProcessBuilder processBuilder =
new ProcessBuilder(commandParts).directory(tmpSolc.getExecutable().getParentFile());
processBuilder
.environment()
.put("LD_LIBRARY_PATH", tmpSolc.getExecutable().getParentFile().getCanonicalPath());
Process process = processBuilder.start();
ParallelReader error = new ParallelReader(process.getErrorStream());
ParallelReader output = new ParallelReader(process.getInputStream());
error.start();
output.start();
try {
process.waitFor();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
boolean success = process.exitValue() == 0;
if (logger.isTraceEnabled()) {
logger.trace(
" source : {}, success: {}, output: {}, error: {} ",
source.getAbsoluteFile(),
success,
output.getContent(),
error.getContent());
}
return new Result(error.getContent(), output.getContent(), success);
}
private List prepareCommandOptions(
Solc solc, boolean optimize, boolean combinedJson, Option... options)
throws IOException {
List commandParts = new ArrayList<>();
commandParts.add(solc.getExecutable().getCanonicalPath());
if (optimize) {
commandParts.add("--" + Options.OPTIMIZE.getName());
}
if (combinedJson) {
Option combinedJsonOption =
new Options.CombinedJson(getElementsOf(OutputOption.class, options));
commandParts.add("--" + combinedJsonOption.getName());
commandParts.add(combinedJsonOption.getValue());
} else {
for (Option option : getElementsOf(OutputOption.class, options)) {
commandParts.add("--" + option.getName());
}
}
for (Option option : getElementsOf(ListOption.class, options)) {
commandParts.add("--" + option.getName());
commandParts.add(option.getValue());
}
for (Option option : getElementsOf(CustomOption.class, options)) {
commandParts.add("--" + option.getName());
if (option.getValue() != null) {
commandParts.add(option.getValue());
}
}
for (Option option : getElementsOf(NameOnlyOption.class, options)) {
commandParts.add("--" + option.getName());
}
return commandParts;
}
private static List getElementsOf(Class clazz, Option... options) {
return Arrays.stream(options).filter(clazz::isInstance).map(clazz::cast).collect(toList());
}
private Result compileSrc(
byte[] source,
boolean sm,
boolean optimize,
boolean combinedJson,
Version version,
Option... options)
throws IOException {
Solc tmpSolc = getInstance().getSolc(sm, version);
List commandParts = prepareCommandOptions(tmpSolc, optimize, combinedJson, options);
// new in solidity 0.5.0: using stdin requires an explicit "-". The following output
// of 'solc' if no file is provided, e.g.,: solc --combined-json abi,bin,interface,metadata
//
// No input files given. If you wish to use the standard input please specify "-"
// explicitly.
//
// For older solc version "-" is not an issue as it is accepet as well
commandParts.add("-");
ProcessBuilder processBuilder =
new ProcessBuilder(commandParts).directory(tmpSolc.getExecutable().getParentFile());
processBuilder
.environment()
.put("LD_LIBRARY_PATH", tmpSolc.getExecutable().getParentFile().getCanonicalPath());
Process process = processBuilder.start();
try (BufferedOutputStream stream = new BufferedOutputStream(process.getOutputStream())) {
stream.write(source);
}
ParallelReader error = new ParallelReader(process.getErrorStream());
ParallelReader output = new ParallelReader(process.getInputStream());
error.start();
output.start();
try {
process.waitFor();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
boolean success = process.exitValue() == 0;
return new Result(error.getContent(), output.getContent(), success);
}
public static String runGetVersionOutput(boolean sm, Version version) throws IOException {
List commandParts = new ArrayList<>();
Solc tmpSolc = getInstance().getSolc(sm, version);
commandParts.add(tmpSolc.getExecutable().getCanonicalPath());
commandParts.add("--" + Options.VERSION.getName());
ProcessBuilder processBuilder =
new ProcessBuilder(commandParts).directory(tmpSolc.getExecutable().getParentFile());
processBuilder
.environment()
.put("LD_LIBRARY_PATH", tmpSolc.getExecutable().getParentFile().getCanonicalPath());
Process process = processBuilder.start();
ParallelReader error = new ParallelReader(process.getErrorStream());
ParallelReader output = new ParallelReader(process.getInputStream());
error.start();
output.start();
try {
process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
if (process.exitValue() == 0) {
return output.getContent();
}
throw new RuntimeException("Problem getting solc version: " + error.getContent());
}
public Solc getSolc(boolean sm, Version version) {
initSolc(sm, version);
return (sm ? sMSolcMap.get(version) : solcMap.get(version));
}
public static SolidityCompiler getInstance() {
if (INSTANCE == null) {
synchronized (SolidityCompiler.class) {
if (INSTANCE == null) {
INSTANCE = new SolidityCompiler();
}
}
}
return INSTANCE;
}
}