func (c *CompilationDatabase) fixArg(arg, buildDir string) string {
	if filepath.IsAbs(arg) {
		return arg
	}
	for _, d := range []string{buildDir, c.root} {
		if dir := filepath.Join(d, arg); osutil.IsExist(dir) {
			return dir
		}
	}
	return arg
}
fixArg return the absolube directory path based by buildDir if contains filepath.Separator.
package compilationdatabase
import (
	"bufio"
	"bytes"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"github.com/go-clang/v3.9/clang"
	"github.com/pkg/errors"
	"github.com/pkgutil/osutil"
	"github.com/zchee/clang-server/internal/log"
)
const DefaultJSONName = "compile_commands.json"
type CompilationDatabase struct {
	root string
	cd             clang.CompilationDatabase
	cmds           []*CompileCommand
	CompilerConfig *CompilerConfig
	mu sync.Mutex
}
type CompilerConfig struct {
	Target              string
	ThreadModel         string
	InstalledDir        string
	DefaultFlag         []string
	Version             string
	SystemCIncludeDir   []string
	SystemCXXIncludeDir []string
	SystemFrameworkDir  []string
}
type CompileCommand struct {
	Directory string `json:"directory"`
	File string `json:"file"`
	Command string `json:"command"`
	Arguments []string `json:"arguments"`
	Output string `json:"output"`
}
func NewCompilationDatabase(root string) *CompilationDatabase {
	return &CompilationDatabase{
		root: root,
	}
}
func (c *CompilationDatabase) Parse(jsonfile string, pathRange []string) error {
	ch := make(chan *CompilerConfig, 1)
	go func() { ch <- c.DefaultCompilerConfig() }()
	if jsonfile == "" {
		jsonfile = DefaultJSONName
	}
	dir := c.findJSONFile(jsonfile, pathRange)
	if dir == "" {
		return errors.Errorf("couldn't find the %s", jsonfile)
	}
	cErr, cd := clang.FromDirectory(dir)
	if cErr != clang.CompilationDatabase_NoError {
		return errors.WithStack(clang.CompilationDatabase_Error(cErr))
	}
	c.cd = cd
	defer c.cd.Dispose()
	c.CompilerConfig = <-ch
	if err := c.parseFlags(); err != nil {
		return errors.WithStack(err)
	}
	return nil
}
func (c *CompilationDatabase) CompileCommands() []*CompileCommand {
	return c.cmds
}
func (c *CompilationDatabase) DefaultCompilerConfig() *CompilerConfig {
	cc := "clang"  
	if envCC := os.Getenv("CC"); envCC != "" {
		cc = envCC
	}
	ccPath, err := exec.LookPath(cc)
	if err != nil {
		log.Fatalf("couldn't find %s", cc)
	}
	cmd := exec.Command(ccPath, "-v", "-x", "c++", "/dev/null", "-fsyntax-only")
	var b bytes.Buffer
	cmd.Stdout = &b
	cmd.Stderr = &b
	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
	cfg := new(CompilerConfig)
	var includeSection bool
	scan := bufio.NewScanner(&b)
	for scan.Scan() {
		s := scan.Text()
		if includeSection {
			if strings.HasPrefix(s, "End") {
				includeSection = false
				break
			}
			path := strings.TrimSpace(s)
			l := len(path)
			switch {
			case path[l-6:l-3] == "c++":
				cfg.SystemCXXIncludeDir = append(cfg.SystemCXXIncludeDir, "-I"+path)
			case strings.Contains(s, "framework directory"):
				cfg.SystemFrameworkDir = append(cfg.SystemFrameworkDir, "-F"+strings.TrimSuffix(path, " (framework directory)"))
			default:
				cfg.SystemCIncludeDir = append(cfg.SystemCIncludeDir, "-I"+path)
			}
		}
		switch {
		case strings.HasPrefix(s, "Target"):
			cfg.Target = strings.TrimPrefix(s, "Target: ")
		case strings.HasPrefix(s, "Thread model"):
			cfg.ThreadModel = strings.TrimPrefix(s, "Thread model: ")
		case strings.HasPrefix(s, "InstalledDir"):
			cfg.InstalledDir = strings.TrimPrefix(s, "InstalledDir: ")
		case (strings.Contains(s, cfg.InstalledDir) && strings.Contains(s, "-cc1")):
			flags := strings.Fields(s)
			cfg.DefaultFlag = flags[1 : len(flags)-1]
		case strings.HasPrefix(s, cc):
			sep := strings.Split(s, "version ")
			version := strings.Split(sep[1], " ")[0]
			cfg.Version = version
		case strings.HasPrefix(s, "#include <...>"):
			includeSection = true
		}
	}
	return cfg
}
func (c *CompilationDatabase) findJSONFile(filename string, pathRange []string) string {
	if pathRange == nil {
		parent := filepath.Dir(c.root)              
		buildDir := filepath.Join(c.root, "build")  
		outDir := filepath.Join(c.root, "out")      
		pathRange = []string{c.root, parent, buildDir, outDir}
	}
	pathCh := make(chan string, 1)
	for _, d := range pathRange {
		go func(d string) {
			if osutil.IsExist(filepath.Join(d, filename)) {
				log.Debugf("found filepath: %s", filepath.Join(d, filename))
				pathCh <- d
			}
		}(d)
	}
	return <-pathCh
}
func (c *CompilationDatabase) parseFlags() error {
	cmds := c.cd.AllCompileCommands()
	ncmds := cmds.Size()
	c.cmds = make([]*CompileCommand, 0, ncmds)
	var wg sync.WaitGroup
	for i := uint32(0); i < ncmds; i++ {
		wg.Add(1)
		i := i
		go func(i uint32) {
			c.mu.Lock()
			defer func() {
				wg.Done()
				c.mu.Unlock()
			}()
			cmd := cmds.Command(i)
			args := c.formatFlag(cmd)
			c.cmds = append(c.cmds, &CompileCommand{
				Directory: cmd.Directory(),
				File:      cmd.Filename(),
				Command:   strings.Join(args, " "),
				Arguments: args,
			})
		}(i)
	}
	wg.Wait()
	return nil
}
func (c *CompilationDatabase) formatFlag(cmd clang.CompileCommand) []string {
	n := cmd.NumArgs()
	flags := make([]string, 0, n)
	for i := uint32(0); i < n; i++ {
		dir := cmd.Directory()
		f := cmd.Arg(i)
		switch {
		case strings.HasPrefix(f, "-I"):  
			includeDir := c.fixArg(strings.TrimPrefix(f, "-I"), dir)
			flags = append(flags, "-I", includeDir)
		case strings.HasPrefix(f, "-F"):  
			frameworkDir := c.fixArg(strings.TrimPrefix(f, "-F"), dir)
			flags = append(flags, "-F", frameworkDir)
		case f == "-I",  
			f == "-F",                  
			f == "-D",                  
			f == "-U",                  
			f == "-framework",          
			f == "-x",                  
			f == "-arch",               
			f == "-include",            
			f == "-isysroot",           
			f == "-isystem",            
			f == "-iframework",         
			f == "-include-pch",        
			f == "-isystem-after",      
			f == "-idirafter",          
			f == "-imacros",            
			f == "-ivfsoverlay",        
			f == "-iwithprefix",        
			f == "-iwithprefixbefore",  
			f == "-iwithsysroot":       
			flags = append(flags, f, c.fixArg(cmd.Arg(i+1), dir))
		case strings.HasPrefix(f, "-D"),  
			strings.HasPrefix(f, "-U"),                      
			strings.HasPrefix(f, "-std"),                    
			strings.HasPrefix(f, "-stdlib"),                 
			strings.HasPrefix(f, "-x"),                      
			strings.HasPrefix(f, "-arch"),                   
			strings.HasPrefix(f, "-mmacosx-version-min"),    
			strings.HasPrefix(f, "-miphoneos-version-min"),  
			strings.HasPrefix(f, "-include"),                
			strings.HasPrefix(f, "-isysroot"),               
			strings.HasPrefix(f, "-isystem"),                
			strings.HasPrefix(f, "-iframework"),             
			strings.HasPrefix(f, "-include-pch"),            
			strings.HasPrefix(f, "-isystem-after"),          
			strings.HasPrefix(f, "-G"),
			strings.HasPrefix(f, "-T"),
			strings.HasPrefix(f, "-V"),
			strings.HasPrefix(f, "-target"),
			strings.HasPrefix(f, "-Xanalyzer"),
			strings.HasPrefix(f, "-Xassembler"),
			strings.HasPrefix(f, "-Xclang"),
			strings.HasPrefix(f, "-Xlinker"),
			strings.HasPrefix(f, "-Xpreprocessor"),
			strings.HasPrefix(f, "-b"),
			strings.HasPrefix(f, "-gcc-toolchain"),
			strings.HasPrefix(f, "-idirafter"),  
			strings.HasPrefix(f, "-imacros"),    
			strings.HasPrefix(f, "-imultilib"),
			strings.HasPrefix(f, "-iprefix"),
			strings.HasPrefix(f, "-ivfsoverlay"),        
			strings.HasPrefix(f, "-iwithprefix"),        
			strings.HasPrefix(f, "-iwithprefixbefore"),  
			strings.HasPrefix(f, "-iwithsysroot"):       
			flags = append(flags, f)
		}
	}
	return flags
}