Skip to content

Commit 1a9e669

Browse files
committed
common/compiler: simplify solc wrapper
Support for legacy version 0.9.x is gone. The compiler version is no longer cached. Compilation results (and the version) are read directly from stdout using the --combined-json flag. As a workaround for ethereum/solidity#651, source code is written to a temporary file before compilation. Integration of solc in package ethapi and cmd/abigen is now much simpler because the compiler wrapper is no longer passed around as a pointer. Fixes ethereum#2806, accidentally
1 parent 84d11c1 commit 1a9e669

File tree

8 files changed

+225
-254
lines changed

8 files changed

+225
-254
lines changed

cmd/abigen/main.go

+1-12
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,7 @@ func main() {
6868
for _, kind := range strings.Split(*excFlag, ",") {
6969
exclude[strings.ToLower(kind)] = true
7070
}
71-
// Build the Solidity source into bindable components
72-
solc, err := compiler.New(*solcFlag)
73-
if err != nil {
74-
fmt.Printf("Failed to locate Solidity compiler: %v\n", err)
75-
os.Exit(-1)
76-
}
77-
source, err := ioutil.ReadFile(*solFlag)
78-
if err != nil {
79-
fmt.Printf("Failed to read Soldity source code: %v\n", err)
80-
os.Exit(-1)
81-
}
82-
contracts, err := solc.Compile(string(source))
71+
contracts, err := compiler.CompileSolidity(*solcFlag, *solFlag)
8372
if err != nil {
8473
fmt.Printf("Failed to build Solidity contract: %v\n", err)
8574
os.Exit(-1)

common/compiler/solidity.go

+93-122
Original file line numberDiff line numberDiff line change
@@ -14,49 +14,31 @@
1414
// You should have received a copy of the GNU Lesser General Public License
1515
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
1616

17+
// Package compiler wraps the Solidity compiler executable (solc).
1718
package compiler
1819

1920
import (
2021
"bytes"
2122
"encoding/json"
2223
"errors"
2324
"fmt"
25+
"io"
2426
"io/ioutil"
2527
"os"
2628
"os/exec"
27-
"path/filepath"
2829
"regexp"
2930
"strings"
3031

3132
"github.com/ethereum/go-ethereum/common"
3233
"github.com/ethereum/go-ethereum/crypto"
33-
"github.com/ethereum/go-ethereum/logger"
34-
"github.com/ethereum/go-ethereum/logger/glog"
3534
)
3635

3736
var (
3837
versionRegexp = regexp.MustCompile("[0-9]+\\.[0-9]+\\.[0-9]+")
39-
legacyRegexp = regexp.MustCompile("0\\.(9\\..*|1\\.[01])")
40-
paramsLegacy = []string{
41-
"--binary", // Request to output the contract in binary (hexadecimal).
42-
"file", //
43-
"--json-abi", // Request to output the contract's JSON ABI interface.
44-
"file", //
45-
"--natspec-user", // Request to output the contract's Natspec user documentation.
46-
"file", //
47-
"--natspec-dev", // Request to output the contract's Natspec developer documentation.
48-
"file",
49-
"--add-std",
50-
"1",
51-
}
52-
paramsNew = []string{
53-
"--bin", // Request to output the contract in binary (hexadecimal).
54-
"--abi", // Request to output the contract's JSON ABI interface.
55-
"--userdoc", // Request to output the contract's Natspec user documentation.
56-
"--devdoc", // Request to output the contract's Natspec developer documentation.
38+
solcParams = []string{
39+
"--combined-json", "bin,abi,userdoc,devdoc",
5740
"--add-std", // include standard lib contracts
5841
"--optimize", // code optimizer switched on
59-
"-o", // output directory
6042
}
6143
)
6244

@@ -76,135 +58,112 @@ type ContractInfo struct {
7658
DeveloperDoc interface{} `json:"developerDoc"`
7759
}
7860

61+
// Solidity contains information about the solidity compiler.
7962
type Solidity struct {
80-
solcPath string
81-
version string
82-
fullVersion string
83-
legacy bool
63+
Path, Version, FullVersion string
8464
}
8565

86-
func New(solcPath string) (sol *Solidity, err error) {
87-
// set default solc
88-
if len(solcPath) == 0 {
89-
solcPath = "solc"
90-
}
91-
solcPath, err = exec.LookPath(solcPath)
92-
if err != nil {
93-
return
94-
}
66+
// --combined-output format
67+
type solcOutput struct {
68+
Contracts map[string]struct{ Bin, Abi, Devdoc, Userdoc string }
69+
Version string
70+
}
9571

96-
cmd := exec.Command(solcPath, "--version")
72+
// SolidityVersion runs solc and parses its version output.
73+
func SolidityVersion(solc string) (*Solidity, error) {
74+
if solc == "" {
75+
solc = "solc"
76+
}
9777
var out bytes.Buffer
78+
cmd := exec.Command(solc, "--version")
9879
cmd.Stdout = &out
99-
err = cmd.Run()
100-
if err != nil {
101-
return
80+
if err := cmd.Run(); err != nil {
81+
return nil, err
10282
}
103-
104-
fullVersion := out.String()
105-
version := versionRegexp.FindString(fullVersion)
106-
legacy := legacyRegexp.MatchString(version)
107-
108-
sol = &Solidity{
109-
solcPath: solcPath,
110-
version: version,
111-
fullVersion: fullVersion,
112-
legacy: legacy,
83+
s := &Solidity{
84+
Path: cmd.Path,
85+
FullVersion: out.String(),
86+
Version: versionRegexp.FindString(out.String()),
11387
}
114-
glog.V(logger.Info).Infoln(sol.Info())
115-
return
116-
}
117-
118-
func (sol *Solidity) Info() string {
119-
return fmt.Sprintf("%s\npath: %s", sol.fullVersion, sol.solcPath)
120-
}
121-
122-
func (sol *Solidity) Version() string {
123-
return sol.version
88+
return s, nil
12489
}
12590

126-
// Compile builds and returns all the contracts contained within a source string.
127-
func (sol *Solidity) Compile(source string) (map[string]*Contract, error) {
128-
// Short circuit if no source code was specified
91+
// CompileSolidityString builds and returns all the contracts contained within a source string.
92+
func CompileSolidityString(solc, source string) (map[string]*Contract, error) {
12993
if len(source) == 0 {
13094
return nil, errors.New("solc: empty source string")
13195
}
132-
// Create a safe place to dump compilation output
133-
wd, err := ioutil.TempDir("", "solc")
96+
if solc == "" {
97+
solc = "solc"
98+
}
99+
// Write source to a temporary file. Compiling stdin used to be supported
100+
// but seems to produce an exception with solc 0.3.5.
101+
infile, err := ioutil.TempFile("", "geth-compile-solidity")
134102
if err != nil {
135-
return nil, fmt.Errorf("solc: failed to create temporary build folder: %v", err)
103+
return nil, err
104+
}
105+
defer os.Remove(infile.Name())
106+
if _, err := io.WriteString(infile, source); err != nil {
107+
return nil, err
108+
}
109+
if err := infile.Close(); err != nil {
110+
return nil, err
136111
}
137-
defer os.RemoveAll(wd)
138112

139-
// Assemble the compiler command, change to the temp folder and capture any errors
140-
stderr := new(bytes.Buffer)
113+
return CompileSolidity(solc, infile.Name())
114+
}
141115

142-
var params []string
143-
if sol.legacy {
144-
params = paramsLegacy
145-
} else {
146-
params = paramsNew
147-
params = append(params, wd)
116+
// CompileSolidity compiles all given Solidity source files.
117+
func CompileSolidity(solc string, sourcefiles ...string) (map[string]*Contract, error) {
118+
if len(sourcefiles) == 0 {
119+
return nil, errors.New("solc: no source ")
120+
}
121+
source, err := slurpFiles(sourcefiles)
122+
if err != nil {
123+
return nil, err
124+
}
125+
if solc == "" {
126+
solc = "solc"
148127
}
149-
compilerOptions := strings.Join(params, " ")
150-
151-
cmd := exec.Command(sol.solcPath, params...)
152-
cmd.Stdin = strings.NewReader(source)
153-
cmd.Stderr = stderr
154128

129+
var stderr, stdout bytes.Buffer
130+
args := append(solcParams, "--")
131+
cmd := exec.Command(solc, append(args, sourcefiles...)...)
132+
cmd.Stderr = &stderr
133+
cmd.Stdout = &stdout
155134
if err := cmd.Run(); err != nil {
156-
return nil, fmt.Errorf("solc: %v\n%s", err, string(stderr.Bytes()))
135+
return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes())
157136
}
158-
// Sanity check that something was actually built
159-
matches, _ := filepath.Glob(filepath.Join(wd, "*.bin*"))
160-
if len(matches) < 1 {
161-
return nil, fmt.Errorf("solc: no build results found")
137+
var output solcOutput
138+
if err := json.Unmarshal(stdout.Bytes(), &output); err != nil {
139+
return nil, err
162140
}
163-
// Compilation succeeded, assemble and return the contracts
164-
contracts := make(map[string]*Contract)
165-
for _, path := range matches {
166-
_, file := filepath.Split(path)
167-
base := strings.Split(file, ".")[0]
168-
169-
// Parse the individual compilation results (code binary, ABI definitions, user and dev docs)
170-
var binary []byte
171-
binext := ".bin"
172-
if sol.legacy {
173-
binext = ".binary"
174-
}
175-
if binary, err = ioutil.ReadFile(filepath.Join(wd, base+binext)); err != nil {
176-
return nil, fmt.Errorf("solc: error reading compiler output for code: %v", err)
177-
}
141+
shortVersion := versionRegexp.FindString(output.Version)
178142

143+
// Compilation succeeded, assemble and return the contracts.
144+
contracts := make(map[string]*Contract)
145+
for name, info := range output.Contracts {
146+
// Parse the individual compilation results.
179147
var abi interface{}
180-
if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".abi")); err != nil {
181-
return nil, fmt.Errorf("solc: error reading abi definition: %v", err)
182-
} else if err = json.Unmarshal(blob, &abi); err != nil {
183-
return nil, fmt.Errorf("solc: error parsing abi definition: %v", err)
148+
if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil {
149+
return nil, fmt.Errorf("solc: error reading abi definition (%v)", err)
184150
}
185-
186151
var userdoc interface{}
187-
if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".docuser")); err != nil {
152+
if err := json.Unmarshal([]byte(info.Userdoc), &userdoc); err != nil {
188153
return nil, fmt.Errorf("solc: error reading user doc: %v", err)
189-
} else if err = json.Unmarshal(blob, &userdoc); err != nil {
190-
return nil, fmt.Errorf("solc: error parsing user doc: %v", err)
191154
}
192-
193155
var devdoc interface{}
194-
if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".docdev")); err != nil {
156+
if err := json.Unmarshal([]byte(info.Devdoc), &devdoc); err != nil {
195157
return nil, fmt.Errorf("solc: error reading dev doc: %v", err)
196-
} else if err = json.Unmarshal(blob, &devdoc); err != nil {
197-
return nil, fmt.Errorf("solc: error parsing dev doc: %v", err)
198158
}
199-
// Assemble the final contract
200-
contracts[base] = &Contract{
201-
Code: "0x" + string(binary),
159+
contracts[name] = &Contract{
160+
Code: "0x" + info.Bin,
202161
Info: ContractInfo{
203162
Source: source,
204163
Language: "Solidity",
205-
LanguageVersion: sol.version,
206-
CompilerVersion: sol.version,
207-
CompilerOptions: compilerOptions,
164+
LanguageVersion: shortVersion,
165+
CompilerVersion: shortVersion,
166+
CompilerOptions: strings.Join(solcParams, " "),
208167
AbiDefinition: abi,
209168
UserDoc: userdoc,
210169
DeveloperDoc: devdoc,
@@ -214,12 +173,24 @@ func (sol *Solidity) Compile(source string) (map[string]*Contract, error) {
214173
return contracts, nil
215174
}
216175

217-
func SaveInfo(info *ContractInfo, filename string) (contenthash common.Hash, err error) {
176+
func slurpFiles(files []string) (string, error) {
177+
var concat bytes.Buffer
178+
for _, file := range files {
179+
content, err := ioutil.ReadFile(file)
180+
if err != nil {
181+
return "", err
182+
}
183+
concat.Write(content)
184+
}
185+
return concat.String(), nil
186+
}
187+
188+
// SaveInfo serializes info to the given file and returns its Keccak256 hash.
189+
func SaveInfo(info *ContractInfo, filename string) (common.Hash, error) {
218190
infojson, err := json.Marshal(info)
219191
if err != nil {
220-
return
192+
return common.Hash{}, err
221193
}
222-
contenthash = common.BytesToHash(crypto.Keccak256(infojson))
223-
err = ioutil.WriteFile(filename, infojson, 0600)
224-
return
194+
contenthash := common.BytesToHash(crypto.Keccak256(infojson))
195+
return contenthash, ioutil.WriteFile(filename, infojson, 0600)
225196
}

0 commit comments

Comments
 (0)