Skip to content

Commit 06a2553

Browse files
Jason Mittertreinerjakepetroules
Jason Mittertreiner
authored andcommitted
Fix the Unit Tests on Windows [Fixed Mismatch]
- Fixing Symlink handling on Windows - The windows process handling I did was broken in a few places - Handling Windows paths - Working around a few Clang/MSVC inconsistencies
1 parent 4e25396 commit 06a2553

File tree

11 files changed

+286
-111
lines changed

11 files changed

+286
-111
lines changed

docs/buildsystem.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ Symlink Tool
437437
This tool is used to create a symbolic link at a particular location, with
438438
appropriate dependency tracking. Due to the nature of symbolic links it is
439439
important to use this tool when creating links during a build, as opposed to the
440-
usuall `shell` tool. The reason why is that the build system will, by default,
440+
usual `shell` tool. The reason why is that the build system will, by default,
441441
use `stat(2)` to examine the contents of output files for the purposes of
442442
evaluating the build state. In the case of a symbolic link this is incorrect, as
443443
it will retrieve the status information of the target, not the link itself. This

include/llbuild/Basic/PlatformUtility.h

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ char *strsep(char **stringp, const char *delim);
4747
// it's path.
4848
std::string makeTmpDir();
4949

50+
// Return a string containing all valid path separators on the current platform
51+
std::string getPathSeparators();
52+
5053
/// Sets the max open file limit to min(max(soft_limit, limit), hard_limit),
5154
/// where soft_limit and hard_limit are gathered from the system.
5255
///

lib/Basic/FileSystem.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,17 @@ class LocalFileSystem : public FileSystem {
171171
return true;
172172
}
173173

174+
#if defined(_WIN32)
175+
// Windows sets EACCES if the file is a directory
176+
if (errno != EACCES) {
177+
return false;
178+
}
179+
#else
174180
// Error can't be that `path` is actually a directory (on Linux `EISDIR` will be returned since 2.1.132).
175181
if (errno != EPERM && errno != EISDIR) {
176182
return false;
177183
}
184+
#endif
178185

179186
// Check if `path` is a directory.
180187
llbuild::basic::sys::StatStruct statbuf;

lib/Basic/PlatformUtility.cpp

+110-24
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
#include "llbuild/Basic/Stat.h"
1212
#include "llvm/ADT/SmallVector.h"
1313
#include "llvm/Support/ConvertUTF.h"
14+
#include "llvm/Support/Path.h"
1415

1516
#if defined(_WIN32)
1617
#include "LeanWindows.h"
1718
#include <Shlwapi.h>
1819
#include <direct.h>
1920
#include <io.h>
21+
#include <time.h>
2022
#else
2123
#include <fnmatch.h>
2224
#include <unistd.h>
@@ -28,7 +30,9 @@ using namespace llbuild::basic;
2830

2931
bool sys::chdir(const char *fileName) {
3032
#if defined(_WIN32)
31-
return SetCurrentDirectoryA(fileName);
33+
llvm::SmallVector<UTF16, 20> wFileName;
34+
llvm::convertUTF8ToUTF16String(fileName, wFileName);
35+
return SetCurrentDirectoryW((LPCWSTR)wFileName.data());
3236
#else
3337
return ::chdir(fileName) == 0;
3438
#endif
@@ -42,11 +46,68 @@ int sys::close(int fileHandle) {
4246
#endif
4347
}
4448

49+
#if defined(_WIN32)
50+
time_t filetimeToTime_t(FILETIME ft) {
51+
long long ltime = ft.dwLowDateTime | ((long long)ft.dwHighDateTime << 32);
52+
return (time_t)((ltime - 116444736000000000) / 10000000);
53+
}
54+
#endif
55+
4556
int sys::lstat(const char *fileName, sys::StatStruct *buf) {
4657
#if defined(_WIN32)
47-
// We deliberately ignore lstat on Windows, and delegate
48-
// to stat.
49-
return ::_stat(fileName, buf);
58+
llvm::SmallVector<UTF16, 20> wfilename;
59+
llvm::convertUTF8ToUTF16String(fileName, wfilename);
60+
HANDLE h = CreateFileW(
61+
/*lpFileName=*/(LPCWSTR)wfilename.data(),
62+
/*dwDesiredAccess=*/0,
63+
/*dwShareMode=*/FILE_SHARE_READ,
64+
/*lpSecurityAttributes=*/NULL,
65+
/*dwCreationDisposition=*/OPEN_EXISTING,
66+
/*dwFlagsAndAttributes=*/FILE_FLAG_OPEN_REPARSE_POINT |
67+
FILE_FLAG_BACKUP_SEMANTICS,
68+
/*hTemplateFile=*/NULL);
69+
if (h == INVALID_HANDLE_VALUE) {
70+
int err = GetLastError();
71+
if (err == ERROR_FILE_NOT_FOUND) {
72+
errno = ENOENT;
73+
}
74+
return -1;
75+
}
76+
BY_HANDLE_FILE_INFORMATION info;
77+
GetFileInformationByHandle(h, &info);
78+
// Group id is always 0 on Windows
79+
buf->st_gid = 0;
80+
buf->st_atime = filetimeToTime_t(info.ftLastAccessTime);
81+
buf->st_ctime = filetimeToTime_t(info.ftCreationTime);
82+
buf->st_dev = info.dwVolumeSerialNumber;
83+
// inodes have meaning on FAT/HPFS/NTFS
84+
buf->st_ino = 0;
85+
buf->st_rdev = info.dwVolumeSerialNumber;
86+
buf->st_mode =
87+
// On a symlink to a directory, Windows sets both the REPARSE_POINT and
88+
// DIRECTORY attributes. Since Windows doesn't provide S_IFLNK and we
89+
// want unix style "symlinks to directories are not directories
90+
// themselves, we say symlinks are regular files
91+
(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
92+
? _S_IFREG
93+
: (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? _S_IFDIR
94+
: _S_IFREG;
95+
buf->st_mode |= (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
96+
? _S_IREAD
97+
: _S_IREAD | _S_IWRITE;
98+
llvm::StringRef extension =
99+
llvm::sys::path::extension(llvm::StringRef(fileName));
100+
if (extension == ".exe" || extension == ".cmd" || extension == ".bat" ||
101+
extension == ".com") {
102+
buf->st_mode |= _S_IEXEC;
103+
}
104+
buf->st_mtime = filetimeToTime_t(info.ftLastWriteTime);
105+
buf->st_nlink = info.nNumberOfLinks;
106+
buf->st_size = ((long long)info.nFileSizeHigh << 32) | info.nFileSizeLow;
107+
// Uid is always 0 on Windows systems
108+
buf->st_uid = 0;
109+
CloseHandle(h);
110+
return 0;
50111
#else
51112
return ::lstat(fileName, buf);
52113
#endif
@@ -109,17 +170,25 @@ int sys::stat(const char *fileName, StatStruct *buf) {
109170
#endif
110171
}
111172

112-
int sys::symlink(const char *source, const char *target) {
173+
// Create a symlink named linkPath which contains the string pointsTo
174+
int sys::symlink(const char *pointsTo, const char *linkPath) {
113175
#if defined(_WIN32)
114-
DWORD attributes = GetFileAttributesA(source);
115-
if (attributes != INVALID_FILE_ATTRIBUTES &&
116-
(attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
117-
return ::CreateSymbolicLinkA(source, target, SYMBOLIC_LINK_FLAG_DIRECTORY);
118-
}
119-
120-
return ::CreateSymbolicLinkA(source, target, 0);
176+
llvm::SmallVector<UTF16, 20> wPointsTo;
177+
llvm::convertUTF8ToUTF16String(pointsTo, wPointsTo);
178+
llvm::SmallVector<UTF16, 20> wLinkPath;
179+
llvm::convertUTF8ToUTF16String(linkPath, wLinkPath);
180+
DWORD attributes = GetFileAttributesW((LPCWSTR)wPointsTo.data());
181+
DWORD directoryFlag = (attributes != INVALID_FILE_ATTRIBUTES &&
182+
attributes & FILE_ATTRIBUTE_DIRECTORY)
183+
? SYMBOLIC_LINK_FLAG_DIRECTORY
184+
: 0;
185+
// Note that CreateSymbolicLinkW takes its arguments in reverse order
186+
// compared to symlink/_symlink
187+
return !::CreateSymbolicLinkW(
188+
(LPCWSTR)wLinkPath.data(), (LPCWSTR)wPointsTo.data(),
189+
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE | directoryFlag);
121190
#else
122-
return ::symlink(source, target);
191+
return ::symlink(pointsTo, linkPath);
123192
#endif
124193
}
125194

@@ -217,23 +286,29 @@ std::string sys::strerror(int error) {
217286

218287
char *sys::strsep(char **stringp, const char *delim) {
219288
#if defined(_WIN32)
220-
char *begin, *end = *stringp;
289+
// If *stringp is NULL, the strsep() function returns NULL and does nothing
290+
// else.
221291
if (*stringp == NULL) {
222292
return NULL;
223293
}
224-
int delimLen = strlen(delim);
225-
bool found = false;
226-
while (*end) {
227-
for (int i = 0; i < delimLen; i++) {
294+
char *begin = *stringp;
295+
char *end = *stringp;
296+
do {
297+
// Otherwise, this function finds the first token in the string *stringp,
298+
// that is delimited by one of the bytes in the string delim.
299+
for (int i = 0; delim[i] != '\0'; i++) {
228300
if (*end == delim[i]) {
229-
found = true;
301+
// This token is terminated by overwriting the delimiter with a null
302+
// byte ('\0'), and *stringp is updated to point past the token.
303+
*end = '\0';
304+
*stringp = end + 1;
305+
return begin;
230306
}
231307
}
232-
if (found) {
233-
*stringp = end + 1;
234-
}
235-
}
236-
*end = '\0';
308+
} while (*(++end));
309+
// In case no delimiter was found, the token is taken to be the entire string
310+
// *stringp, and *stringp is made NULL.
311+
*stringp = NULL;
237312
return begin;
238313
#else
239314
return ::strsep(stringp, delim);
@@ -244,9 +319,20 @@ std::string sys::makeTmpDir() {
244319
#if defined(_WIN32)
245320
char path[MAX_PATH];
246321
tmpnam_s(path, MAX_PATH);
322+
llvm::SmallVector<UTF16, 20> wPath;
323+
llvm::convertUTF8ToUTF16String(path, wPath);
324+
CreateDirectoryW((LPCWSTR)wPath.data(), NULL);
247325
return std::string(path);
248326
#else
249327
char tmpDirPathBuf[] = "/tmp/fileXXXXXX";
250328
return std::string(mkdtemp(tmpDirPathBuf));
251329
#endif
252330
}
331+
332+
std::string sys::getPathSeparators() {
333+
#if defined(_WIN32)
334+
return "/\\";
335+
#else
336+
return "/";
337+
#endif
338+
}

lib/Basic/Subprocess.cpp

+27-28
Original file line numberDiff line numberDiff line change
@@ -285,11 +285,10 @@ static void cleanUpExecutedProcess(ProcessDelegate& delegate,
285285
FILETIME stimeTicks;
286286
int waitResult = WaitForSingleObject(pid, INFINITE);
287287
int err = GetLastError();
288-
DWORD exitCode;
288+
DWORD exitCode = 0;
289289
GetExitCodeProcess(pid, &exitCode);
290290

291-
if (exitCode != 0 || waitResult == WAIT_FAILED ||
292-
waitResult == WAIT_ABANDONED) {
291+
if (waitResult == WAIT_FAILED || waitResult == WAIT_ABANDONED) {
293292
auto result = ProcessResult::makeFailed(exitCode);
294293
delegate.processHadError(ctx, handle,
295294
Twine("unable to wait for process (") +
@@ -298,6 +297,25 @@ static void cleanUpExecutedProcess(ProcessDelegate& delegate,
298297
completionFn(result);
299298
return;
300299
}
300+
#else
301+
// Wait for the command to complete.
302+
struct rusage usage;
303+
int exitCode, result = wait4(pid, &exitCode, 0, &usage);
304+
while (result == -1 && errno == EINTR)
305+
result = wait4(pid, &exitCode, 0, &usage);
306+
#endif
307+
// Close the release pipe
308+
//
309+
// Note: We purposely hold this open until after the process has finished as
310+
// it simplifies client implentation. If we close it early, clients need to be
311+
// aware of and potentially handle a SIGPIPE.
312+
if (releaseFd >= 0) {
313+
sys::FileDescriptorTraits<>::Close(releaseFd);
314+
}
315+
316+
// Update the set of spawned processes.
317+
pgrp.remove(pid);
318+
#if defined(_WIN32)
301319
PROCESS_MEMORY_COUNTERS counters;
302320
bool res =
303321
GetProcessTimes(pid, &creationTime, &exitTime, &stimeTicks, &utimeTicks);
@@ -317,9 +335,6 @@ static void cleanUpExecutedProcess(ProcessDelegate& delegate,
317335
((uint64_t)stimeTicks.dwHighDateTime << 32 | stimeTicks.dwLowDateTime) /
318336
10;
319337
GetProcessMemoryInfo(pid, &counters, sizeof(counters));
320-
sys::FileDescriptorTraits<>::Close(releaseFd);
321-
322-
pgrp.remove(pid);
323338

324339
// We report additional info in the tracing interval
325340
// - user time, in µs
@@ -330,32 +345,16 @@ static void cleanUpExecutedProcess(ProcessDelegate& delegate,
330345
// subprocess (probably as a new point sample).
331346

332347
// Notify of the process completion.
333-
ProcessStatus processStatus = ProcessStatus::Succeeded;
348+
ProcessStatus processStatus =
349+
(exitCode == 0) ? ProcessStatus::Succeeded : ProcessStatus::Failed;
334350
ProcessResult processResult(processStatus, exitCode, pid, utime, stime,
335351
counters.PeakWorkingSetSize);
336352
#else // !defined(_WIN32)
337-
// Wait for the command to complete.
338-
struct rusage usage;
339-
int exitCode, result = wait4(pid, &exitCode, 0, &usage);
340-
while (result == -1 && errno == EINTR)
341-
result = wait4(pid, &exitCode, 0, &usage);
342-
343-
// Close the release pipe
344-
//
345-
// Note: We purposely hold this open until after the process has finished as
346-
// it simplifies client implentation. If we close it early, clients need to be
347-
// aware of and potentially handle a SIGPIPE.
348-
if (releaseFd >= 0) {
349-
sys::FileDescriptorTraits<>::Close(releaseFd);
350-
}
351-
352-
// Update the set of spawned processes.
353-
pgrp.remove(pid);
354-
355353
if (result == -1) {
356354
auto result = ProcessResult::makeFailed(exitCode);
357355
delegate.processHadError(ctx, handle,
358-
Twine("unable to wait for process (") + strerror(errno) + ")");
356+
Twine("unable to wait for process (") +
357+
strerror(errno) + ")");
359358
delegate.processFinished(ctx, handle, result);
360359
completionFn(result);
361360
return;
@@ -439,8 +438,8 @@ void llbuild::basic::spawnProcess(
439438
startupInfo.dwFlags = STARTF_USESTDHANDLES;
440439
// Set NUL as stdin
441440
HANDLE nul =
442-
CreateFileA("NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
443-
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
441+
CreateFileW(L"NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
442+
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
444443
startupInfo.hStdInput = nul;
445444
#else
446445
// Initialize the spawn attributes.

lib/BuildSystem/BuildSystem.cpp

+24-9
Original file line numberDiff line numberDiff line change
@@ -3453,11 +3453,11 @@ class SymlinkCommand : public Command {
34533453
// FIXME: Need to use the filesystem interfaces.
34543454
bsci.getDelegate().commandStarted(this);
34553455
auto success = true;
3456-
if (llvm::sys::fs::create_link(contents, outputPath)) {
3456+
if (basic::sys::symlink(contents.c_str(), outputPath.str().c_str())) {
34573457
// On failure, we attempt to unlink the file and retry.
34583458
basic::sys::unlink(outputPath.str().c_str());
3459-
3460-
if (llvm::sys::fs::create_link(contents, outputPath)) {
3459+
3460+
if (basic::sys::symlink(contents.c_str(), outputPath.str().c_str())) {
34613461
getBuildSystem(bsci.getBuildEngine()).getDelegate().commandHadError(this,
34623462
"unable to create symlink at '" + outputPath.str() + "'");
34633463
success = false;
@@ -3650,7 +3650,7 @@ class StaleFileRemovalCommand : public Command {
36503650
BuildValue priorValue;
36513651
bool hasPriorResult = false;
36523652

3653-
char path_separator = llvm::sys::path::get_separator()[0];
3653+
std::string pathSeparators = llbuild::basic::sys::getPathSeparators();
36543654

36553655
virtual void configureDescription(const ConfigureContext&, StringRef value) override {
36563656
description = value;
@@ -3790,7 +3790,8 @@ class StaleFileRemovalCommand : public Command {
37903790
bool isLocatedUnderRootPath = roots.size() == 0 ? true : false;
37913791

37923792
// If root paths are defined, stale file paths should be absolute.
3793-
if (roots.size() > 0 && fileToDelete[0] != path_separator) {
3793+
if (roots.size() > 0 &&
3794+
pathSeparators.find(fileToDelete[0]) == std::string::npos) {
37943795
bsci.getDelegate().commandHadWarning(this, "Stale file '" + fileToDelete + "' has a relative path. This is invalid in combination with the root path attribute.\n");
37953796
continue;
37963797
}
@@ -4011,11 +4012,25 @@ void BuildSystem::resetForBuild() {
40114012
}
40124013

40134014
// This function checks if the given path is prefixed by another path.
4014-
bool llbuild::buildsystem::pathIsPrefixedByPath(std::string path, std::string prefixPath) {
4015-
static char path_separator = llvm::sys::path::get_separator()[0];
4015+
bool llbuild::buildsystem::pathIsPrefixedByPath(std::string path,
4016+
std::string prefixPath) {
4017+
std::string pathSeparators = llbuild::basic::sys::getPathSeparators();
4018+
// Note: GCC 4.8 doesn't support the mismatch(first1, last1, first2, last2)
4019+
// overload, just mismatch(first1, last1, first2), so we have to handle the
4020+
// case where prefixPath is longer than path.
4021+
if (prefixPath.length() > path.length()) {
4022+
// The only case where the prefix can be longer and still be a valid prefix
4023+
// is "/foo/" is a prefix of "/foo"
4024+
return prefixPath.substr(0, prefixPath.length() - 1) == path &&
4025+
pathSeparators.find(prefixPath[prefixPath.length() - 1]) !=
4026+
std::string::npos;
4027+
}
40164028
auto res = std::mismatch(prefixPath.begin(), prefixPath.end(), path.begin());
40174029
// Check if `prefixPath` has been exhausted or just a separator remains.
4018-
bool isPrefix = res.first == prefixPath.end() || (*(res.first++) == path_separator);
4030+
bool isPrefix = res.first == prefixPath.end() ||
4031+
(pathSeparators.find(*(res.first++)) != std::string::npos);
40194032
// Check if `path` has been exhausted or just a separator remains.
4020-
return isPrefix && (res.second == path.end() || (*(res.second++) == path_separator));
4033+
return isPrefix &&
4034+
(res.second == path.end() ||
4035+
(pathSeparators.find(*(res.second++)) != std::string::npos));
40214036
}

0 commit comments

Comments
 (0)