14
14
// You should have received a copy of the GNU Lesser General Public License
15
15
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16
16
17
+ // Package compiler wraps the Solidity compiler executable (solc).
17
18
package compiler
18
19
19
20
import (
20
21
"bytes"
21
22
"encoding/json"
22
23
"errors"
23
24
"fmt"
25
+ "io"
24
26
"io/ioutil"
25
27
"os"
26
28
"os/exec"
27
- "path/filepath"
28
29
"regexp"
29
30
"strings"
30
31
31
32
"github.com/ethereum/go-ethereum/common"
32
33
"github.com/ethereum/go-ethereum/crypto"
33
- "github.com/ethereum/go-ethereum/logger"
34
- "github.com/ethereum/go-ethereum/logger/glog"
35
34
)
36
35
37
36
var (
38
37
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" ,
57
40
"--add-std" , // include standard lib contracts
58
41
"--optimize" , // code optimizer switched on
59
- "-o" , // output directory
60
42
}
61
43
)
62
44
@@ -76,135 +58,112 @@ type ContractInfo struct {
76
58
DeveloperDoc interface {} `json:"developerDoc"`
77
59
}
78
60
61
+ // Solidity contains information about the solidity compiler.
79
62
type Solidity struct {
80
- solcPath string
81
- version string
82
- fullVersion string
83
- legacy bool
63
+ Path , Version , FullVersion string
84
64
}
85
65
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
+ }
95
71
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
+ }
97
77
var out bytes.Buffer
78
+ cmd := exec .Command (solc , "--version" )
98
79
cmd .Stdout = & out
99
- err = cmd .Run ()
100
- if err != nil {
101
- return
80
+ if err := cmd .Run (); err != nil {
81
+ return nil , err
102
82
}
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 ()),
113
87
}
114
- glog .V (logger .Info ).Infoln (sol .Info ())
115
- return
116
- }
117
-
118
- func (sol * Solidity ) Info () string {
119
- return fmt .Sprintf ("%s\n path: %s" , sol .fullVersion , sol .solcPath )
120
- }
121
-
122
- func (sol * Solidity ) Version () string {
123
- return sol .version
88
+ return s , nil
124
89
}
125
90
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 ) {
129
93
if len (source ) == 0 {
130
94
return nil , errors .New ("solc: empty source string" )
131
95
}
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" )
134
102
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
136
111
}
137
- defer os .RemoveAll (wd )
138
112
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
+ }
141
115
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"
148
127
}
149
- compilerOptions := strings .Join (params , " " )
150
-
151
- cmd := exec .Command (sol .solcPath , params ... )
152
- cmd .Stdin = strings .NewReader (source )
153
- cmd .Stderr = stderr
154
128
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
155
134
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 ())
157
136
}
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
162
140
}
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 )
178
142
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.
179
147
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 )
184
150
}
185
-
186
151
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 {
188
153
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 )
191
154
}
192
-
193
155
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 {
195
157
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 )
198
158
}
199
- // Assemble the final contract
200
- contracts [base ] = & Contract {
201
- Code : "0x" + string (binary ),
159
+ contracts [name ] = & Contract {
160
+ Code : "0x" + info .Bin ,
202
161
Info : ContractInfo {
203
162
Source : source ,
204
163
Language : "Solidity" ,
205
- LanguageVersion : sol . version ,
206
- CompilerVersion : sol . version ,
207
- CompilerOptions : compilerOptions ,
164
+ LanguageVersion : shortVersion ,
165
+ CompilerVersion : shortVersion ,
166
+ CompilerOptions : strings . Join ( solcParams , " " ) ,
208
167
AbiDefinition : abi ,
209
168
UserDoc : userdoc ,
210
169
DeveloperDoc : devdoc ,
@@ -214,12 +173,24 @@ func (sol *Solidity) Compile(source string) (map[string]*Contract, error) {
214
173
return contracts , nil
215
174
}
216
175
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 ) {
218
190
infojson , err := json .Marshal (info )
219
191
if err != nil {
220
- return
192
+ return common. Hash {}, err
221
193
}
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 )
225
196
}
0 commit comments