go-hep.org/x/hep@v0.38.1/groot/internal/rtests/rtests.go (about) 1 // Copyright ©2018 The go-hep Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package rtests 6 7 import ( 8 "errors" 9 "fmt" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "strings" 14 15 "go-hep.org/x/hep/groot/rbytes" 16 "go-hep.org/x/hep/groot/root" 17 ) 18 19 type ROOTer interface { 20 root.Object 21 rbytes.Marshaler 22 rbytes.Unmarshaler 23 } 24 25 func XrdRemote(fname string) string { 26 const remote = "root://ccxrootdgotest.in2p3.fr:9001/tmp/rootio" 27 return remote + "/" + fname 28 } 29 30 var ( 31 HasROOT = false // HasROOT is true when a C++ ROOT installation could be detected. 32 ErrNoROOT = errors.New("rtests: no C++ ROOT installed") 33 rootCmd = "" 34 rootCling = "" 35 ) 36 37 // RunCxxROOT executes the function fct in the provided C++ code with optional arguments args. 38 // RunCxxROOT creates a temporary file named '<fct>.C' from the provided C++ code and 39 // executes it via ROOT C++. 40 // RunCxxROOT returns the combined stdout/stderr output and an error, if any. 41 // If 'fct' ends with a '+', RunCxxROOT will run the macro through ACliC. 42 func RunCxxROOT(fct string, code []byte, args ...any) ([]byte, error) { 43 aclic := "" 44 if strings.HasSuffix(fct, "+") { 45 aclic = "+" 46 } 47 fct = strings.TrimRight(fct, "+") 48 tmp, err := os.MkdirTemp("", "groot-rtests-") 49 if err != nil { 50 return nil, fmt.Errorf("could not create tmpdir: %w", err) 51 } 52 defer os.RemoveAll(tmp) 53 54 // create a dummy header file for ROOT-dictionary generation purposes. 55 _ = os.WriteFile(filepath.Join(tmp, "__groot-Event.h"), []byte(""), 0644) 56 57 fname := filepath.Join(tmp, fct+".C") 58 err = os.WriteFile(fname, []byte(code), 0644) 59 if err != nil { 60 return nil, fmt.Errorf("could not generate ROOT macro %q: %w", fname, err) 61 } 62 63 o := new(strings.Builder) 64 fmt.Fprintf(o, "%s%s(", fname, aclic) 65 for i, arg := range args { 66 format := "" 67 if i > 0 { 68 format = ", " 69 } 70 switch arg.(type) { 71 case string: 72 format += "%q" 73 default: 74 format += "%v" 75 } 76 fmt.Fprintf(o, format, arg) 77 } 78 fmt.Fprintf(o, ")") 79 80 if !HasROOT { 81 return nil, ErrNoROOT 82 } 83 84 cmd := exec.Command(rootCmd, "-l", "-b", "-x", "-q", o.String()) 85 out, err := cmd.CombinedOutput() 86 if err != nil { 87 return out, ROOTError{Err: err, Cmd: cmd.Path, Args: cmd.Args, Out: out} 88 } 89 90 return out, nil 91 } 92 93 // GenROOTDictCode generates the ROOT dictionary code from the given event 94 // and linkdef definitions. 95 // GenROOTDictCode invokes rootcling and returns the generated code. 96 func GenROOTDictCode(event, linkdef string) ([]byte, error) { 97 if !HasROOT { 98 return nil, ErrNoROOT 99 } 100 101 tmp, err := os.MkdirTemp("", "groot-rtests-") 102 if err != nil { 103 return nil, fmt.Errorf("rtests: could not create tmp dir: %w", err) 104 } 105 defer os.RemoveAll(tmp) 106 107 var ( 108 fname = filepath.Join(tmp, "__groot-Event.h") 109 link = filepath.Join(tmp, "LinkDef.h") 110 dname = filepath.Join(tmp, "dict.cxx") 111 ) 112 113 err = os.WriteFile(fname, []byte(event), 0644) 114 if err != nil { 115 return nil, fmt.Errorf("rtests: could not write event header file: %w", err) 116 } 117 118 err = os.WriteFile(link, []byte(linkdef), 0644) 119 if err != nil { 120 return nil, fmt.Errorf("rtests: could not write event header file: %w", err) 121 } 122 123 cmd := exec.Command( 124 rootCling, 125 filepath.Base(dname), 126 filepath.Base(fname), 127 filepath.Base(link), 128 ) 129 cmd.Dir = tmp 130 131 out, err := cmd.CombinedOutput() 132 if err != nil { 133 return nil, ROOTError{Err: err, Cmd: cmd.Path, Args: cmd.Args, Out: out} 134 } 135 136 dict, err := os.ReadFile(dname) 137 if err != nil { 138 return nil, fmt.Errorf("rtests: could not read dict file: %w", err) 139 } 140 141 return dict, nil 142 } 143 144 type ROOTError struct { 145 Err error 146 Cmd string 147 Args []string 148 Out []byte 149 } 150 151 func (err ROOTError) Error() string { 152 return fmt.Sprintf( 153 "could not run '%s': %v\noutput:\n%s", 154 strings.Join(append([]string{err.Cmd}, err.Args...), " "), 155 err.Err, 156 err.Out, 157 ) 158 } 159 160 func (err ROOTError) Unwrap() error { 161 return err.Err 162 } 163 164 func init() { 165 cmd, err := exec.LookPath("root.exe") 166 if err != nil { 167 return 168 } 169 HasROOT = true 170 rootCmd = cmd 171 172 cmd, err = exec.LookPath("rootcling") 173 if err != nil { 174 cmd, err = exec.LookPath("rootcling.exe") 175 } 176 177 if err != nil { 178 return 179 } 180 rootCling = cmd 181 } 182 183 var ( 184 _ error = (*ROOTError)(nil) 185 _ interface{ Unwrap() error } = (*ROOTError)(nil) 186 )