github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/util/compiler_contract.go (about) 1 // Copyright (c) 2022 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package util 7 8 import ( 9 "bytes" 10 "os" 11 "os/exec" 12 "regexp" 13 "strconv" 14 "strings" 15 16 "github.com/ethereum/go-ethereum/common/compiler" 17 "github.com/pkg/errors" 18 ) 19 20 var versionRegexp = regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`) 21 22 // Solidity contains information about the solidity compiler. 23 type Solidity struct { 24 Path, Version, FullVersion string 25 Major, Minor, Patch int 26 ExtraAllowedPath []string 27 } 28 29 // SolidityVersion runs solc and parses its version output. 30 func SolidityVersion(solc string) (*Solidity, error) { 31 if solc == "" { 32 solc = "solc" 33 } 34 var out bytes.Buffer 35 cmd := exec.Command(solc, "--version") 36 cmd.Stdout = &out 37 err := cmd.Run() 38 if err != nil { 39 return nil, err 40 } 41 matches := versionRegexp.FindStringSubmatch(out.String()) 42 if len(matches) != 4 { 43 return nil, errors.Errorf("can't parse solc version %q", out.String()) 44 } 45 s := &Solidity{Path: cmd.Path, FullVersion: out.String(), Version: matches[0]} 46 if s.Major, err = strconv.Atoi(matches[1]); err != nil { 47 return nil, err 48 } 49 if s.Minor, err = strconv.Atoi(matches[2]); err != nil { 50 return nil, err 51 } 52 if s.Patch, err = strconv.Atoi(matches[3]); err != nil { 53 return nil, err 54 } 55 return s, nil 56 } 57 58 // CompileSolidityString builds and returns all the contracts contained within a source string. 59 func CompileSolidityString(solc, source string) (map[string]*compiler.Contract, error) { 60 if len(source) == 0 { 61 return nil, errors.New("solc: empty source string") 62 } 63 s, err := SolidityVersion(solc) 64 if err != nil { 65 return nil, err 66 } 67 return s.CompileSource(source) 68 } 69 70 // CompileSolidity compiles all given Solidity source files. 71 func CompileSolidity(solc string, sourcefiles ...string) (map[string]*compiler.Contract, error) { 72 if len(sourcefiles) == 0 { 73 return nil, errors.New("solc: no source files") 74 } 75 s, err := SolidityVersion(solc) 76 if err != nil { 77 return nil, err 78 } 79 80 return s.CompileFiles(sourcefiles...) 81 } 82 83 // CompileSource builds and returns all the contracts contained within a source string. 84 func (s *Solidity) CompileSource(source string) (map[string]*compiler.Contract, error) { 85 args := append(s.makeArgs(), "--") 86 cmd := exec.Command(s.Path, append(args, "-")...) 87 cmd.Stdin = strings.NewReader(source) 88 return s.run(cmd, source) 89 } 90 91 // CompileFiles compiles all given Solidity source files. 92 func (s *Solidity) CompileFiles(sourcefiles ...string) (map[string]*compiler.Contract, error) { 93 source, err := slurpFiles(sourcefiles) 94 if err != nil { 95 return nil, err 96 } 97 args := append(s.makeArgs(), "--") 98 cmd := exec.Command(s.Path, append(args, sourcefiles...)...) 99 return s.run(cmd, source) 100 } 101 102 func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*compiler.Contract, error) { 103 var stderr, stdout bytes.Buffer 104 cmd.Stderr = &stderr 105 cmd.Stdout = &stdout 106 if err := cmd.Run(); err != nil { 107 return nil, errors.Errorf("solc: %v\n%s", err, stderr.Bytes()) 108 } 109 return compiler.ParseCombinedJSON(stdout.Bytes(), source, s.Version, s.Version, strings.Join(s.makeArgs(), " ")) 110 } 111 112 func (s *Solidity) allowedPaths() string { 113 paths := []string{".", "./", "../"} // default to support relative paths 114 if len(s.ExtraAllowedPath) > 0 { 115 paths = append(paths, s.ExtraAllowedPath...) 116 } 117 return strings.Join(paths, ", ") 118 } 119 120 func (s *Solidity) makeArgs() []string { 121 p := []string{ 122 "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc", 123 "--optimize", // code optimizer switched on 124 "--allow-paths", s.allowedPaths(), 125 } 126 if s.Major > 0 || s.Minor > 4 || s.Patch > 6 { 127 p[1] += ",metadata,hashes" 128 } 129 return p 130 } 131 132 func slurpFiles(files []string) (string, error) { 133 var concat bytes.Buffer 134 for _, file := range files { 135 content, err := os.ReadFile(file) 136 if err != nil { 137 return "", err 138 } 139 concat.Write(content) 140 } 141 return concat.String(), nil 142 }