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  )