Skip to content

Commit

Permalink
correctly expand __FILE__ on a server-side by using relative paths (#6)
Browse files Browse the repository at this point in the history
Before, a client send cppInFile as an abs file name, and a server launched
> g++ /{clientWorkingDir}/{cppInFile}
Now, a client sends cppInFile exactly the same as it was specified in cmd line,
and besides, it sends cwd.
On a server, we launch g++ in a dir {clientWorkingDir}/{cwd} and pass
> g++ {cppInFile}
directly, as a relative one, which leads to __FILE__
expanded equally to client launch.
  • Loading branch information
tolk-vm authored Mar 22, 2023
1 parent ab52095 commit edc04d3
Show file tree
Hide file tree
Showing 20 changed files with 307 additions and 214 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
RELEASE = v1.1
RELEASE = v1.1.2
BUILD_COMMIT := $(shell git rev-parse --short HEAD)
DATE := $(shell date -u '+%F %X UTC')
VERSION := ${RELEASE}, rev ${BUILD_COMMIT}, compiled at ${DATE}
Expand Down
2 changes: 1 addition & 1 deletion cmd/nocc-daemon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func main() {
failedStart("no remote hosts set; you should set NOCC_SERVERS or NOCC_SERVERS_FILENAME")
}

exitCode, stdout, stderr := client.EmulateDaemonInsideThisProcessForDev(remoteNoccHosts, os.Args[1:], *disableOwnIncludes)
exitCode, stdout, stderr := client.EmulateDaemonInsideThisProcessForDev(remoteNoccHosts, os.Args[1:], *disableOwnIncludes, 1)
_, _ = os.Stdout.Write(stdout)
_, _ = os.Stderr.Write(stderr)
os.Exit(exitCode)
Expand Down
4 changes: 2 additions & 2 deletions internal/client/compile-locally.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func (localCxx *LocalCxxLaunch) RunCxxLocally() (exitCode int, stdout []byte, st

// EmulateDaemonInsideThisProcessForDev is for dev purposes:
// for development, I use `nocc-daemon g++ ...` from GoLand directly (without a C++ `nocc` wrapper).
func EmulateDaemonInsideThisProcessForDev(remoteNoccHosts []string, cmdLine []string, disableOwnIncludes bool) (exitCode int, stdout []byte, stderr []byte) {
daemon, err := MakeDaemon(remoteNoccHosts, false, disableOwnIncludes, 1)
func EmulateDaemonInsideThisProcessForDev(remoteNoccHosts []string, cmdLine []string, disableOwnIncludes bool, localCxxQueueSize int) (exitCode int, stdout []byte, stderr []byte) {
daemon, err := MakeDaemon(remoteNoccHosts, false, disableOwnIncludes, int64(localCxxQueueSize))
if err != nil {
panic(err)
}
Expand Down
6 changes: 3 additions & 3 deletions internal/client/compile-remotely.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import (
// CompileCppRemotely executes all steps of remote compilation (see comments inside the function).
// On success, it saves the resulting .o file — the same as if compiled locally.
// It's called within a daemon for every Invocation of type invokedForCompilingCpp.
func CompileCppRemotely(daemon *Daemon, invocation *Invocation, remote *RemoteConnection) (exitCode int, stdout []byte, stderr []byte, err error) {
func CompileCppRemotely(daemon *Daemon, cwd string, invocation *Invocation, remote *RemoteConnection) (exitCode int, stdout []byte, stderr []byte, err error) {
invocation.wgRecv.Add(1)

// 1. For an input .cpp file, find all dependent .h/.nocc-pch/etc. that are required for compilation
hFiles, cppFile, err := invocation.CollectDependentIncludes(daemon.disableOwnIncludes)
hFiles, cppFile, err := invocation.CollectDependentIncludes(cwd, daemon.disableOwnIncludes)
if err != nil {
return 0, nil, nil, fmt.Errorf("failed to collect depencies: %v", err)
}
Expand Down Expand Up @@ -47,7 +47,7 @@ func CompileCppRemotely(daemon *Daemon, invocation *Invocation, remote *RemoteCo

// 2. Send sha256 of the .cpp and all dependencies to the remote.
// The remote returns indexes that are missing (needed to be uploaded).
fileIndexesToUpload, err := remote.StartCompilationSession(invocation, requiredFiles)
fileIndexesToUpload, err := remote.StartCompilationSession(invocation, cwd, requiredFiles)
if err != nil {
return 0, nil, nil, err
}
Expand Down
11 changes: 9 additions & 2 deletions internal/client/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type Daemon struct {

disableObjCache bool
disableOwnIncludes bool
disableLocalCxx bool

totalInvocations uint32
activeInvocations map[uint32]*Invocation
Expand Down Expand Up @@ -88,6 +89,7 @@ func MakeDaemon(remoteNoccHosts []string, disableObjCache bool, disableOwnInclud
localCxxThrottle: make(chan struct{}, localCxxQueueSize),
disableOwnIncludes: disableOwnIncludes,
disableObjCache: disableObjCache,
disableLocalCxx: localCxxQueueSize == 0,
activeInvocations: make(map[uint32]*Invocation, 300),
includesCache: make(map[string]*IncludesCache, 1),
}
Expand Down Expand Up @@ -171,7 +173,7 @@ func (daemon *Daemon) HandleInvocation(req DaemonSockRequest) DaemonSockResponse

case invokedForCompilingPch:
invocation.includesCache.Clear()
ownPch, err := GenerateOwnPch(daemon, invocation)
ownPch, err := GenerateOwnPch(daemon, req.Cwd, invocation)
if err != nil {
return daemon.FallbackToLocalCxx(req, fmt.Errorf("failed to generate pch file: %v", err))
}
Expand Down Expand Up @@ -212,7 +214,7 @@ func (daemon *Daemon) HandleInvocation(req DaemonSockRequest) DaemonSockResponse

var err error
var reply DaemonSockResponse
reply.ExitCode, reply.Stdout, reply.Stderr, err = CompileCppRemotely(daemon, invocation, remote)
reply.ExitCode, reply.Stdout, reply.Stderr, err = CompileCppRemotely(daemon, req.Cwd, invocation, remote)

daemon.mu.Lock()
delete(daemon.activeInvocations, invocation.sessionID)
Expand All @@ -233,6 +235,11 @@ func (daemon *Daemon) FallbackToLocalCxx(req DaemonSockRequest, reason error) Da
}

var reply DaemonSockResponse
if daemon.disableLocalCxx {
reply.ExitCode = 1
reply.Stderr = []byte("fallback to local cxx disabled")
return reply
}

daemon.localCxxThrottle <- struct{}{}
localCxx := LocalCxxLaunch{req.CmdLine, req.Cwd}
Expand Down
22 changes: 6 additions & 16 deletions internal/client/dep-cmd-flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ type DepCmdFlags struct {
flagMD bool // -MD (like -MF {def file})
flagMMD bool // -MMD (mention only user header files, not system header files)
flagMP bool // -MP (add a phony target for each dependency other than the main file)

origO string // if -MT not set, -o used as a target name (not resolved objOutFile, but as-is from cmdLine)
origCpp string // a first dependency is an input cpp file, but again, as-is, not resolved cppInFile
}

func (deps *DepCmdFlags) SetCmdFlagMF(absFilename string) {
Expand Down Expand Up @@ -62,14 +59,6 @@ func (deps *DepCmdFlags) SetCmdFlagMP() {
deps.flagMP = true
}

func (deps *DepCmdFlags) SetCmdOutputFile(origO string) {
deps.origO = origO
}

func (deps *DepCmdFlags) SetCmdInputFile(origCpp string) {
deps.origCpp = origCpp
}

// ShouldGenerateDepFile determines whether to output .o.d file besides .o compilation
func (deps *DepCmdFlags) ShouldGenerateDepFile() bool {
return deps.flagMD || deps.flagMF != ""
Expand All @@ -81,7 +70,7 @@ func (deps *DepCmdFlags) ShouldGenerateDepFile() bool {
func (deps *DepCmdFlags) GenerateAndSaveDepFile(invocation *Invocation, hFiles []*IncludedFile) (string, error) {
targetName := deps.flagMT
if len(targetName) == 0 {
targetName = deps.calcDefaultTargetName()
targetName = deps.calcDefaultTargetName(invocation)
}

depFileName := deps.calcOutputDepFileName(invocation)
Expand All @@ -94,7 +83,7 @@ func (deps *DepCmdFlags) GenerateAndSaveDepFile(invocation *Invocation, hFiles [
// > This option instructs CPP to add a phony target for each dependency other than the main file,
// > causing each to depend on nothing.
for idx, depStr := range depListMainTarget {
if idx > 0 { // 0 is origCpp
if idx > 0 { // 0 is cppInFile
depTargets = append(depTargets, DepFileTarget{escapeMakefileSpaces(depStr), nil})
}
}
Expand All @@ -108,9 +97,10 @@ func (deps *DepCmdFlags) GenerateAndSaveDepFile(invocation *Invocation, hFiles [
}

// calcDefaultTargetName returns targetName if no -MT and similar options passed
func (deps *DepCmdFlags) calcDefaultTargetName() string {
func (deps *DepCmdFlags) calcDefaultTargetName(invocation *Invocation) string {
// g++ documentation doesn't satisfy its actual behavior, the implementation seems to be just
return deps.origO
// (remember, that objOutFile is not a full path, it's a relative as specified in cmd line)
return invocation.objOutFile
}

// calcOutputDepFileName returns a name of generated .o.d file based on cmd flags
Expand Down Expand Up @@ -145,7 +135,7 @@ func (deps *DepCmdFlags) calcDepListFromHFiles(invocation *Invocation, hFiles []
}

depList := make([]string, 0, 1+len(hFiles))
depList = append(depList, quoteMakefileTarget(deps.origCpp))
depList = append(depList, quoteMakefileTarget(invocation.cppInFile))
for _, hFile := range hFiles {
depList = append(depList, relFileName(hFile.fileName))
}
Expand Down
3 changes: 2 additions & 1 deletion internal/client/includes-collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (file *IncludedFile) ToPbFileMetadata() *pb.FileMetadata {
// Since cxx knows nothing about .nocc-pch files, it will output all dependencies regardless of -fpch-preprocess flag.
// We'll manually add .nocc-pch if found, so the remote is supposed to use it, not its nested dependencies, actually.
// See https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html
func CollectDependentIncludesByCxxM(includesCache *IncludesCache, cxxName string, cppInFile string, cxxArgs []string, cxxIDirs IncludeDirs) (hFiles []*IncludedFile, cppFile IncludedFile, err error) {
func CollectDependentIncludesByCxxM(includesCache *IncludesCache, cwd string, cxxName string, cppInFile string, cxxArgs []string, cxxIDirs IncludeDirs) (hFiles []*IncludedFile, cppFile IncludedFile, err error) {
cxxCmdLine := make([]string, 0, len(cxxArgs)+2*cxxIDirs.Count()+4)
cxxCmdLine = append(cxxCmdLine, cxxArgs...)
cxxCmdLine = append(cxxCmdLine, cxxIDirs.AsCxxArgs()...)
Expand All @@ -56,6 +56,7 @@ func CollectDependentIncludesByCxxM(includesCache *IncludesCache, cxxName string
}

cxxMCommand := exec.Command(cxxName, cxxCmdLine...)
cxxMCommand.Dir = cwd
var cxxMStdout, cxxMStderr bytes.Buffer
cxxMCommand.Stdout = &cxxMStdout
cxxMCommand.Stderr = &cxxMStderr
Expand Down
37 changes: 23 additions & 14 deletions internal/client/invocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ type Invocation struct {
sessionID uint32 // incremental while a daemon is alive

// cmdLine is parsed to the following fields:
cppInFile string // absolute path to the input file (.cpp for compilation, .h for pch generation)
objOutFile string // absolute path to the output file (.o for compilation, .gch/.pch for pch generation)
cppInFile string // input file as specified in cmd line (.cpp for compilation, .h for pch generation)
objOutFile string // output file as specified in cmd line (.o for compilation, .gch/.pch for pch generation)
cxxName string // g++ / clang / etc.
cxxArgs []string // args like -Wall, -fpch-preprocess and many more, except:
cxxIDirs IncludeDirs // -I / -iquote / -isystem go here
Expand Down Expand Up @@ -89,13 +89,13 @@ func ParseCmdLineInvocation(daemon *Daemon, cwd string, cmdLine []string) (invoc
if cmdLine[*argIndex] == "-Xclang" { // -Xclang -include -Xclang {file}
*argIndex++
}
return pathAbs(cwd, cmdLine[*argIndex]), true
return cmdLine[*argIndex], true
} else {
invocation.err = fmt.Errorf("unsupported command-line: no argument after %s", arg)
return "", false
}
} else if strings.HasPrefix(arg, key) { // -I/path
return pathAbs(cwd, arg[len(key):]), true
return arg[len(key):], true
}
return "", false
}
Expand All @@ -121,19 +121,18 @@ func ParseCmdLineInvocation(daemon *Daemon, cwd string, cmdLine []string) (invoc
if arg[0] == '-' {
if oFile, ok := parseArgFile("-o", arg, &i); ok {
invocation.objOutFile = oFile
invocation.depsFlags.SetCmdOutputFile(strings.TrimPrefix(arg, "-o"))
continue
} else if dir, ok := parseArgFile("-I", arg, &i); ok {
invocation.cxxIDirs.dirsI = append(invocation.cxxIDirs.dirsI, dir)
invocation.cxxIDirs.dirsI = append(invocation.cxxIDirs.dirsI, pathAbs(cwd, dir))
continue
} else if dir, ok := parseArgFile("-iquote", arg, &i); ok {
invocation.cxxIDirs.dirsIquote = append(invocation.cxxIDirs.dirsIquote, dir)
invocation.cxxIDirs.dirsIquote = append(invocation.cxxIDirs.dirsIquote, pathAbs(cwd, dir))
continue
} else if dir, ok := parseArgFile("-isystem", arg, &i); ok {
invocation.cxxIDirs.dirsIsystem = append(invocation.cxxIDirs.dirsIsystem, dir)
invocation.cxxIDirs.dirsIsystem = append(invocation.cxxIDirs.dirsIsystem, pathAbs(cwd, dir))
continue
} else if iFile, ok := parseArgFile("-include", arg, &i); ok {
invocation.cxxIDirs.filesI = append(invocation.cxxIDirs.filesI, iFile)
invocation.cxxIDirs.filesI = append(invocation.cxxIDirs.filesI, pathAbs(cwd, iFile))
continue
} else if arg == "-march=native" {
invocation.err = fmt.Errorf("-march=native can't be launched remotely")
Expand Down Expand Up @@ -190,8 +189,7 @@ func ParseCmdLineInvocation(daemon *Daemon, cwd string, cmdLine []string) (invoc
invocation.err = fmt.Errorf("unsupported command-line: multiple input source files")
return
}
invocation.cppInFile = pathAbs(cwd, arg)
invocation.depsFlags.SetCmdInputFile(arg)
invocation.cppInFile = arg
continue
} else if strings.HasSuffix(arg, ".o") || strings.HasPrefix(arg, ".so") || strings.HasSuffix(arg, ".a") {
invocation.invokeType = invokedForLinking
Expand Down Expand Up @@ -221,15 +219,26 @@ func ParseCmdLineInvocation(daemon *Daemon, cwd string, cmdLine []string) (invoc
// There are two modes of finding dependencies:
// 1. Natively: invoke "cxx -M" (it invokes preprocessor only).
// 2. Own includes parser, which works much faster and theoretically should return the same (or a bit more) results.
func (invocation *Invocation) CollectDependentIncludes(disableOwnIncludes bool) (hFiles []*IncludedFile, cppFile IncludedFile, err error) {
func (invocation *Invocation) CollectDependentIncludes(cwd string, disableOwnIncludes bool) (hFiles []*IncludedFile, cppFile IncludedFile, err error) {
cppInFileAbs := invocation.GetCppInFileAbs(cwd)

if disableOwnIncludes {
return CollectDependentIncludesByCxxM(invocation.includesCache, invocation.cxxName, invocation.cppInFile, invocation.cxxArgs, invocation.cxxIDirs)
return CollectDependentIncludesByCxxM(invocation.includesCache, cwd, invocation.cxxName, cppInFileAbs, invocation.cxxArgs, invocation.cxxIDirs)
}

includeDirs := invocation.cxxIDirs
includeDirs.MergeWith(invocation.includesCache.cxxDefIDirs)

return CollectDependentIncludesByOwnParser(invocation.includesCache, invocation.cppInFile, includeDirs)
return CollectDependentIncludesByOwnParser(invocation.includesCache, cppInFileAbs, includeDirs)
}

// GetCppInFileAbs returns an absolute path to invocation.cppInFile.
// (remember, that it's stored as-is from cmd line)
func (invocation *Invocation) GetCppInFileAbs(cwd string) string {
if invocation.cppInFile[0] == '/' {
return invocation.cppInFile
}
return cwd + "/" + invocation.cppInFile
}

func (invocation *Invocation) DoneRecvObj(err error) {
Expand Down
4 changes: 2 additions & 2 deletions internal/client/pch-generation.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
// When we need to generate .gch/.pch on a client side, we generate .nocc-pch INSTEAD.
// This file is later discovered as a dependency, and after being uploaded, is compiled to real .gch/.pch on remote.
// See comments above common.OwnPch.
func GenerateOwnPch(daemon *Daemon, invocation *Invocation) (*common.OwnPch, error) {
func GenerateOwnPch(daemon *Daemon, cwd string, invocation *Invocation) (*common.OwnPch, error) {
ownPch := &common.OwnPch{
OwnPchFile: common.ReplaceFileExt(invocation.objOutFile, ".nocc-pch"),
OrigHFile: invocation.cppInFile,
Expand All @@ -21,7 +21,7 @@ func GenerateOwnPch(daemon *Daemon, invocation *Invocation) (*common.OwnPch, err
}
_ = os.Remove(ownPch.OwnPchFile) // if a previous version exists

hFiles, inHFile, err := invocation.CollectDependentIncludes(daemon.disableOwnIncludes)
hFiles, inHFile, err := invocation.CollectDependentIncludes(cwd, daemon.disableOwnIncludes)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion internal/client/remote-connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func MakeRemoteConnection(daemon *Daemon, remoteHostPort string, uploadStreamsCo
// one `nocc` Invocation for cpp compilation == one server.Session, by design.
// As an input, we send metadata about all dependencies needed for a .cpp to be compiled (.h/.nocc-pch/etc.).
// As an output, the remote responds with files that are missing and needed to be uploaded.
func (remote *RemoteConnection) StartCompilationSession(invocation *Invocation, requiredFiles []*pb.FileMetadata) ([]uint32, error) {
func (remote *RemoteConnection) StartCompilationSession(invocation *Invocation, cwd string, requiredFiles []*pb.FileMetadata) ([]uint32, error) {
if remote.isUnavailable {
return nil, fmt.Errorf("remote %s is unavailable", remote.remoteHostPort)
}
Expand All @@ -86,6 +86,7 @@ func (remote *RemoteConnection) StartCompilationSession(invocation *Invocation,
&pb.StartCompilationSessionRequest{
ClientID: remote.clientID,
SessionID: invocation.sessionID,
Cwd: cwd,
CppInFile: invocation.cppInFile,
CxxName: invocation.cxxName,
CxxArgs: invocation.cxxArgs,
Expand Down
23 changes: 18 additions & 5 deletions internal/server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package server

import (
"fmt"
"math/rand"
"os"
"path"
"strconv"
"strings"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -74,29 +72,44 @@ func (client *Client) makeNewFile(clientFileName string, fileSize int64, fileSHA

// MapClientFileNameToServerAbs converts a client file name to an absolute path on server.
// For example, /proj/1.cpp maps to /tmp/nocc-server/clients/{clientID}/proj/1.cpp.
// Note, that system files like /usr/local/include are required to be equal on both sides.
// (if not, a server session will fail to start, and a client will fall back to local compilation)
func (client *Client) MapClientFileNameToServerAbs(clientFileName string) string {
if clientFileName[0] == '/' {
if IsSystemHeaderPath(clientFileName) {
return clientFileName
}
return client.workingDir + clientFileName
}
return path.Join(client.workingDir, clientFileName)
}

// MapServerAbsToClientFileName converts an absolute path on server relatively to the client working dir.
// For example, /tmp/nocc-server/clients/{clientID}/proj/1.cpp maps to /proj/1.cpp.
// If serverFileName is /usr/local/include, it's left as is.
func (client *Client) MapServerAbsToClientFileName(serverFileName string) string {
return strings.TrimPrefix(serverFileName, client.workingDir)
}

func (client *Client) StartNewSession(in *pb.StartCompilationSessionRequest) (*Session, error) {
cppInFile := client.MapClientFileNameToServerAbs(in.CppInFile)
newSession := &Session{
sessionID: in.SessionID,
files: make([]*fileInClientDir, len(in.RequiredFiles)),
cxxName: in.CxxName,
cppInFile: cppInFile,
objOutFile: cppInFile + "." + strconv.Itoa(int(rand.Int31())) + ".o",
cppInFile: in.CppInFile, // as specified in a client cmd line invocation (relative to in.Cwd or abs on a client file system)
objOutFile: os.TempDir() + fmt.Sprintf("/%s.%d.%s.o", client.clientID, in.SessionID, path.Base(in.CppInFile)),
client: client,
}

// old clients that don't send this field (they send abs cppInFile)
// todo delete later, after upgrading all clients
if in.Cwd == "" {
newSession.cxxCwd = client.workingDir
newSession.cppInFile = client.MapClientFileNameToServerAbs(newSession.cppInFile)
} else {
newSession.cxxCwd = client.MapClientFileNameToServerAbs(in.Cwd)
}

newSession.cxxCmdLine = newSession.PrepareServerCxxCmdLine(in.CxxArgs, in.CxxIDirs)

for index, meta := range in.RequiredFiles {
Expand Down
Loading

0 comments on commit edc04d3

Please sign in to comment.