Skip to content

Commit edc04d3

Browse files
authored
correctly expand __FILE__ on a server-side by using relative paths (#6)
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.
1 parent ab52095 commit edc04d3

20 files changed

+307
-214
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
RELEASE = v1.1
1+
RELEASE = v1.1.2
22
BUILD_COMMIT := $(shell git rev-parse --short HEAD)
33
DATE := $(shell date -u '+%F %X UTC')
44
VERSION := ${RELEASE}, rev ${BUILD_COMMIT}, compiled at ${DATE}

cmd/nocc-daemon/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func main() {
154154
failedStart("no remote hosts set; you should set NOCC_SERVERS or NOCC_SERVERS_FILENAME")
155155
}
156156

157-
exitCode, stdout, stderr := client.EmulateDaemonInsideThisProcessForDev(remoteNoccHosts, os.Args[1:], *disableOwnIncludes)
157+
exitCode, stdout, stderr := client.EmulateDaemonInsideThisProcessForDev(remoteNoccHosts, os.Args[1:], *disableOwnIncludes, 1)
158158
_, _ = os.Stdout.Write(stdout)
159159
_, _ = os.Stderr.Write(stderr)
160160
os.Exit(exitCode)

internal/client/compile-locally.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ func (localCxx *LocalCxxLaunch) RunCxxLocally() (exitCode int, stdout []byte, st
4040

4141
// EmulateDaemonInsideThisProcessForDev is for dev purposes:
4242
// for development, I use `nocc-daemon g++ ...` from GoLand directly (without a C++ `nocc` wrapper).
43-
func EmulateDaemonInsideThisProcessForDev(remoteNoccHosts []string, cmdLine []string, disableOwnIncludes bool) (exitCode int, stdout []byte, stderr []byte) {
44-
daemon, err := MakeDaemon(remoteNoccHosts, false, disableOwnIncludes, 1)
43+
func EmulateDaemonInsideThisProcessForDev(remoteNoccHosts []string, cmdLine []string, disableOwnIncludes bool, localCxxQueueSize int) (exitCode int, stdout []byte, stderr []byte) {
44+
daemon, err := MakeDaemon(remoteNoccHosts, false, disableOwnIncludes, int64(localCxxQueueSize))
4545
if err != nil {
4646
panic(err)
4747
}

internal/client/compile-remotely.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import (
1010
// CompileCppRemotely executes all steps of remote compilation (see comments inside the function).
1111
// On success, it saves the resulting .o file — the same as if compiled locally.
1212
// It's called within a daemon for every Invocation of type invokedForCompilingCpp.
13-
func CompileCppRemotely(daemon *Daemon, invocation *Invocation, remote *RemoteConnection) (exitCode int, stdout []byte, stderr []byte, err error) {
13+
func CompileCppRemotely(daemon *Daemon, cwd string, invocation *Invocation, remote *RemoteConnection) (exitCode int, stdout []byte, stderr []byte, err error) {
1414
invocation.wgRecv.Add(1)
1515

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

4848
// 2. Send sha256 of the .cpp and all dependencies to the remote.
4949
// The remote returns indexes that are missing (needed to be uploaded).
50-
fileIndexesToUpload, err := remote.StartCompilationSession(invocation, requiredFiles)
50+
fileIndexesToUpload, err := remote.StartCompilationSession(invocation, cwd, requiredFiles)
5151
if err != nil {
5252
return 0, nil, nil, err
5353
}

internal/client/daemon.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type Daemon struct {
4141

4242
disableObjCache bool
4343
disableOwnIncludes bool
44+
disableLocalCxx bool
4445

4546
totalInvocations uint32
4647
activeInvocations map[uint32]*Invocation
@@ -88,6 +89,7 @@ func MakeDaemon(remoteNoccHosts []string, disableObjCache bool, disableOwnInclud
8889
localCxxThrottle: make(chan struct{}, localCxxQueueSize),
8990
disableOwnIncludes: disableOwnIncludes,
9091
disableObjCache: disableObjCache,
92+
disableLocalCxx: localCxxQueueSize == 0,
9193
activeInvocations: make(map[uint32]*Invocation, 300),
9294
includesCache: make(map[string]*IncludesCache, 1),
9395
}
@@ -171,7 +173,7 @@ func (daemon *Daemon) HandleInvocation(req DaemonSockRequest) DaemonSockResponse
171173

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

213215
var err error
214216
var reply DaemonSockResponse
215-
reply.ExitCode, reply.Stdout, reply.Stderr, err = CompileCppRemotely(daemon, invocation, remote)
217+
reply.ExitCode, reply.Stdout, reply.Stderr, err = CompileCppRemotely(daemon, req.Cwd, invocation, remote)
216218

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

235237
var reply DaemonSockResponse
238+
if daemon.disableLocalCxx {
239+
reply.ExitCode = 1
240+
reply.Stderr = []byte("fallback to local cxx disabled")
241+
return reply
242+
}
236243

237244
daemon.localCxxThrottle <- struct{}{}
238245
localCxx := LocalCxxLaunch{req.CmdLine, req.Cwd}

internal/client/dep-cmd-flags.go

+6-16
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ type DepCmdFlags struct {
2727
flagMD bool // -MD (like -MF {def file})
2828
flagMMD bool // -MMD (mention only user header files, not system header files)
2929
flagMP bool // -MP (add a phony target for each dependency other than the main file)
30-
31-
origO string // if -MT not set, -o used as a target name (not resolved objOutFile, but as-is from cmdLine)
32-
origCpp string // a first dependency is an input cpp file, but again, as-is, not resolved cppInFile
3330
}
3431

3532
func (deps *DepCmdFlags) SetCmdFlagMF(absFilename string) {
@@ -62,14 +59,6 @@ func (deps *DepCmdFlags) SetCmdFlagMP() {
6259
deps.flagMP = true
6360
}
6461

65-
func (deps *DepCmdFlags) SetCmdOutputFile(origO string) {
66-
deps.origO = origO
67-
}
68-
69-
func (deps *DepCmdFlags) SetCmdInputFile(origCpp string) {
70-
deps.origCpp = origCpp
71-
}
72-
7362
// ShouldGenerateDepFile determines whether to output .o.d file besides .o compilation
7463
func (deps *DepCmdFlags) ShouldGenerateDepFile() bool {
7564
return deps.flagMD || deps.flagMF != ""
@@ -81,7 +70,7 @@ func (deps *DepCmdFlags) ShouldGenerateDepFile() bool {
8170
func (deps *DepCmdFlags) GenerateAndSaveDepFile(invocation *Invocation, hFiles []*IncludedFile) (string, error) {
8271
targetName := deps.flagMT
8372
if len(targetName) == 0 {
84-
targetName = deps.calcDefaultTargetName()
73+
targetName = deps.calcDefaultTargetName(invocation)
8574
}
8675

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

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

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

147137
depList := make([]string, 0, 1+len(hFiles))
148-
depList = append(depList, quoteMakefileTarget(deps.origCpp))
138+
depList = append(depList, quoteMakefileTarget(invocation.cppInFile))
149139
for _, hFile := range hFiles {
150140
depList = append(depList, relFileName(hFile.fileName))
151141
}

internal/client/includes-collector.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func (file *IncludedFile) ToPbFileMetadata() *pb.FileMetadata {
4141
// Since cxx knows nothing about .nocc-pch files, it will output all dependencies regardless of -fpch-preprocess flag.
4242
// We'll manually add .nocc-pch if found, so the remote is supposed to use it, not its nested dependencies, actually.
4343
// See https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html
44-
func CollectDependentIncludesByCxxM(includesCache *IncludesCache, cxxName string, cppInFile string, cxxArgs []string, cxxIDirs IncludeDirs) (hFiles []*IncludedFile, cppFile IncludedFile, err error) {
44+
func CollectDependentIncludesByCxxM(includesCache *IncludesCache, cwd string, cxxName string, cppInFile string, cxxArgs []string, cxxIDirs IncludeDirs) (hFiles []*IncludedFile, cppFile IncludedFile, err error) {
4545
cxxCmdLine := make([]string, 0, len(cxxArgs)+2*cxxIDirs.Count()+4)
4646
cxxCmdLine = append(cxxCmdLine, cxxArgs...)
4747
cxxCmdLine = append(cxxCmdLine, cxxIDirs.AsCxxArgs()...)
@@ -56,6 +56,7 @@ func CollectDependentIncludesByCxxM(includesCache *IncludesCache, cxxName string
5656
}
5757

5858
cxxMCommand := exec.Command(cxxName, cxxCmdLine...)
59+
cxxMCommand.Dir = cwd
5960
var cxxMStdout, cxxMStderr bytes.Buffer
6061
cxxMCommand.Stdout = &cxxMStdout
6162
cxxMCommand.Stderr = &cxxMStderr

internal/client/invocation.go

+23-14
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ type Invocation struct {
2727
sessionID uint32 // incremental while a daemon is alive
2828

2929
// cmdLine is parsed to the following fields:
30-
cppInFile string // absolute path to the input file (.cpp for compilation, .h for pch generation)
31-
objOutFile string // absolute path to the output file (.o for compilation, .gch/.pch for pch generation)
30+
cppInFile string // input file as specified in cmd line (.cpp for compilation, .h for pch generation)
31+
objOutFile string // output file as specified in cmd line (.o for compilation, .gch/.pch for pch generation)
3232
cxxName string // g++ / clang / etc.
3333
cxxArgs []string // args like -Wall, -fpch-preprocess and many more, except:
3434
cxxIDirs IncludeDirs // -I / -iquote / -isystem go here
@@ -89,13 +89,13 @@ func ParseCmdLineInvocation(daemon *Daemon, cwd string, cmdLine []string) (invoc
8989
if cmdLine[*argIndex] == "-Xclang" { // -Xclang -include -Xclang {file}
9090
*argIndex++
9191
}
92-
return pathAbs(cwd, cmdLine[*argIndex]), true
92+
return cmdLine[*argIndex], true
9393
} else {
9494
invocation.err = fmt.Errorf("unsupported command-line: no argument after %s", arg)
9595
return "", false
9696
}
9797
} else if strings.HasPrefix(arg, key) { // -I/path
98-
return pathAbs(cwd, arg[len(key):]), true
98+
return arg[len(key):], true
9999
}
100100
return "", false
101101
}
@@ -121,19 +121,18 @@ func ParseCmdLineInvocation(daemon *Daemon, cwd string, cmdLine []string) (invoc
121121
if arg[0] == '-' {
122122
if oFile, ok := parseArgFile("-o", arg, &i); ok {
123123
invocation.objOutFile = oFile
124-
invocation.depsFlags.SetCmdOutputFile(strings.TrimPrefix(arg, "-o"))
125124
continue
126125
} else if dir, ok := parseArgFile("-I", arg, &i); ok {
127-
invocation.cxxIDirs.dirsI = append(invocation.cxxIDirs.dirsI, dir)
126+
invocation.cxxIDirs.dirsI = append(invocation.cxxIDirs.dirsI, pathAbs(cwd, dir))
128127
continue
129128
} else if dir, ok := parseArgFile("-iquote", arg, &i); ok {
130-
invocation.cxxIDirs.dirsIquote = append(invocation.cxxIDirs.dirsIquote, dir)
129+
invocation.cxxIDirs.dirsIquote = append(invocation.cxxIDirs.dirsIquote, pathAbs(cwd, dir))
131130
continue
132131
} else if dir, ok := parseArgFile("-isystem", arg, &i); ok {
133-
invocation.cxxIDirs.dirsIsystem = append(invocation.cxxIDirs.dirsIsystem, dir)
132+
invocation.cxxIDirs.dirsIsystem = append(invocation.cxxIDirs.dirsIsystem, pathAbs(cwd, dir))
134133
continue
135134
} else if iFile, ok := parseArgFile("-include", arg, &i); ok {
136-
invocation.cxxIDirs.filesI = append(invocation.cxxIDirs.filesI, iFile)
135+
invocation.cxxIDirs.filesI = append(invocation.cxxIDirs.filesI, pathAbs(cwd, iFile))
137136
continue
138137
} else if arg == "-march=native" {
139138
invocation.err = fmt.Errorf("-march=native can't be launched remotely")
@@ -190,8 +189,7 @@ func ParseCmdLineInvocation(daemon *Daemon, cwd string, cmdLine []string) (invoc
190189
invocation.err = fmt.Errorf("unsupported command-line: multiple input source files")
191190
return
192191
}
193-
invocation.cppInFile = pathAbs(cwd, arg)
194-
invocation.depsFlags.SetCmdInputFile(arg)
192+
invocation.cppInFile = arg
195193
continue
196194
} else if strings.HasSuffix(arg, ".o") || strings.HasPrefix(arg, ".so") || strings.HasSuffix(arg, ".a") {
197195
invocation.invokeType = invokedForLinking
@@ -221,15 +219,26 @@ func ParseCmdLineInvocation(daemon *Daemon, cwd string, cmdLine []string) (invoc
221219
// There are two modes of finding dependencies:
222220
// 1. Natively: invoke "cxx -M" (it invokes preprocessor only).
223221
// 2. Own includes parser, which works much faster and theoretically should return the same (or a bit more) results.
224-
func (invocation *Invocation) CollectDependentIncludes(disableOwnIncludes bool) (hFiles []*IncludedFile, cppFile IncludedFile, err error) {
222+
func (invocation *Invocation) CollectDependentIncludes(cwd string, disableOwnIncludes bool) (hFiles []*IncludedFile, cppFile IncludedFile, err error) {
223+
cppInFileAbs := invocation.GetCppInFileAbs(cwd)
224+
225225
if disableOwnIncludes {
226-
return CollectDependentIncludesByCxxM(invocation.includesCache, invocation.cxxName, invocation.cppInFile, invocation.cxxArgs, invocation.cxxIDirs)
226+
return CollectDependentIncludesByCxxM(invocation.includesCache, cwd, invocation.cxxName, cppInFileAbs, invocation.cxxArgs, invocation.cxxIDirs)
227227
}
228228

229229
includeDirs := invocation.cxxIDirs
230230
includeDirs.MergeWith(invocation.includesCache.cxxDefIDirs)
231231

232-
return CollectDependentIncludesByOwnParser(invocation.includesCache, invocation.cppInFile, includeDirs)
232+
return CollectDependentIncludesByOwnParser(invocation.includesCache, cppInFileAbs, includeDirs)
233+
}
234+
235+
// GetCppInFileAbs returns an absolute path to invocation.cppInFile.
236+
// (remember, that it's stored as-is from cmd line)
237+
func (invocation *Invocation) GetCppInFileAbs(cwd string) string {
238+
if invocation.cppInFile[0] == '/' {
239+
return invocation.cppInFile
240+
}
241+
return cwd + "/" + invocation.cppInFile
233242
}
234243

235244
func (invocation *Invocation) DoneRecvObj(err error) {

internal/client/pch-generation.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
// When we need to generate .gch/.pch on a client side, we generate .nocc-pch INSTEAD.
1111
// This file is later discovered as a dependency, and after being uploaded, is compiled to real .gch/.pch on remote.
1212
// See comments above common.OwnPch.
13-
func GenerateOwnPch(daemon *Daemon, invocation *Invocation) (*common.OwnPch, error) {
13+
func GenerateOwnPch(daemon *Daemon, cwd string, invocation *Invocation) (*common.OwnPch, error) {
1414
ownPch := &common.OwnPch{
1515
OwnPchFile: common.ReplaceFileExt(invocation.objOutFile, ".nocc-pch"),
1616
OrigHFile: invocation.cppInFile,
@@ -21,7 +21,7 @@ func GenerateOwnPch(daemon *Daemon, invocation *Invocation) (*common.OwnPch, err
2121
}
2222
_ = os.Remove(ownPch.OwnPchFile) // if a previous version exists
2323

24-
hFiles, inHFile, err := invocation.CollectDependentIncludes(daemon.disableOwnIncludes)
24+
hFiles, inHFile, err := invocation.CollectDependentIncludes(cwd, daemon.disableOwnIncludes)
2525
if err != nil {
2626
return nil, err
2727
}

internal/client/remote-connection.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func MakeRemoteConnection(daemon *Daemon, remoteHostPort string, uploadStreamsCo
7676
// one `nocc` Invocation for cpp compilation == one server.Session, by design.
7777
// As an input, we send metadata about all dependencies needed for a .cpp to be compiled (.h/.nocc-pch/etc.).
7878
// As an output, the remote responds with files that are missing and needed to be uploaded.
79-
func (remote *RemoteConnection) StartCompilationSession(invocation *Invocation, requiredFiles []*pb.FileMetadata) ([]uint32, error) {
79+
func (remote *RemoteConnection) StartCompilationSession(invocation *Invocation, cwd string, requiredFiles []*pb.FileMetadata) ([]uint32, error) {
8080
if remote.isUnavailable {
8181
return nil, fmt.Errorf("remote %s is unavailable", remote.remoteHostPort)
8282
}
@@ -86,6 +86,7 @@ func (remote *RemoteConnection) StartCompilationSession(invocation *Invocation,
8686
&pb.StartCompilationSessionRequest{
8787
ClientID: remote.clientID,
8888
SessionID: invocation.sessionID,
89+
Cwd: cwd,
8990
CppInFile: invocation.cppInFile,
9091
CxxName: invocation.cxxName,
9192
CxxArgs: invocation.cxxArgs,

internal/server/client.go

+18-5
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package server
22

33
import (
44
"fmt"
5-
"math/rand"
65
"os"
76
"path"
8-
"strconv"
97
"strings"
108
"sync"
119
"sync/atomic"
@@ -74,29 +72,44 @@ func (client *Client) makeNewFile(clientFileName string, fileSize int64, fileSHA
7472

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

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

9094
func (client *Client) StartNewSession(in *pb.StartCompilationSessionRequest) (*Session, error) {
91-
cppInFile := client.MapClientFileNameToServerAbs(in.CppInFile)
9295
newSession := &Session{
9396
sessionID: in.SessionID,
9497
files: make([]*fileInClientDir, len(in.RequiredFiles)),
9598
cxxName: in.CxxName,
96-
cppInFile: cppInFile,
97-
objOutFile: cppInFile + "." + strconv.Itoa(int(rand.Int31())) + ".o",
99+
cppInFile: in.CppInFile, // as specified in a client cmd line invocation (relative to in.Cwd or abs on a client file system)
100+
objOutFile: os.TempDir() + fmt.Sprintf("/%s.%d.%s.o", client.clientID, in.SessionID, path.Base(in.CppInFile)),
98101
client: client,
99102
}
103+
104+
// old clients that don't send this field (they send abs cppInFile)
105+
// todo delete later, after upgrading all clients
106+
if in.Cwd == "" {
107+
newSession.cxxCwd = client.workingDir
108+
newSession.cppInFile = client.MapClientFileNameToServerAbs(newSession.cppInFile)
109+
} else {
110+
newSession.cxxCwd = client.MapClientFileNameToServerAbs(in.Cwd)
111+
}
112+
100113
newSession.cxxCmdLine = newSession.PrepareServerCxxCmdLine(in.CxxArgs, in.CxxIDirs)
101114

102115
for index, meta := range in.RequiredFiles {

0 commit comments

Comments
 (0)