Files
nt-lite/sdktools/watch.c
T

192 lines
6.4 KiB
C

/* Small external watch supervisor. Each build runs in a fresh child process. */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shellapi.h>
#include <wchar.h>
#include <stdio.h>
#include <stdlib.h>
#include <wctype.h>
struct WatchState {
HANDLE directory;
HANDLE changed;
volatile LONG generation;
};
/* Exclude generated object directories, caches and build logs. */
static int IgnoreChange(const WCHAR *name, DWORD length)
{
WCHAR component[260];
DWORD i = 0;
while (i < length) {
DWORD j = 0;
while (i < length && (name[i] == L'\\' || name[i] == L'/')) ++i;
while (i < length && name[i] != L'\\' && name[i] != L'/') {
if (j + 1 < 260) component[j++] = towlower(name[i]);
++i;
}
component[j] = 0;
if (!wcscmp(component, L"obj") || !wcscmp(component, L"objd") ||
!wcscmp(component, L"out") || !wcscmp(component, L".git") ||
!wcscmp(component, L".vs")) return 1;
if (i >= length) {
if (!wcscmp(component, L"build.dat") ||
!wcscmp(component, L"build.log") ||
!wcscmp(component, L"build.err") ||
!wcscmp(component, L"build.wrn") ||
!wcscmp(component, L"build.inc")) return 1;
}
}
return 0;
}
static DWORD WINAPI WatchThread(void *param)
{
struct WatchState *state = (struct WatchState *)param;
BYTE buffer[32768];
DWORD bytes;
for (;;) {
if (!ReadDirectoryChangesW(state->directory, buffer, sizeof(buffer), TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION,
&bytes, NULL, NULL)) return 1;
if (bytes) {
BYTE *cursor = buffer;
for (;;) {
FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION *)cursor;
if (!IgnoreChange(info->FileName, info->FileNameLength / sizeof(WCHAR))) {
InterlockedIncrement(&state->generation);
SetEvent(state->changed);
break;
}
if (!info->NextEntryOffset) break;
cursor += info->NextEntryOffset;
}
}
}
}
/* Quote an individual argument according to the Win32 CRT argv rules. */
static void AddQuoted(WCHAR *dst, const WCHAR *src, size_t *position)
{
size_t slash = 0;
dst[(*position)++] = L'"';
while (*src) {
if (*src == L'\\') { ++slash; ++src; continue; }
if (*src == L'"') {
while (slash--) dst[(*position)++] = L'\\';
dst[(*position)++] = L'\\';
dst[(*position)++] = L'"';
slash = 0;
++src;
continue;
}
while (slash) { dst[(*position)++] = L'\\'; --slash; }
dst[(*position)++] = *src++;
}
while (slash--) { dst[(*position)++] = L'\\'; dst[(*position)++] = L'\\'; }
dst[(*position)++] = L'"';
dst[*position] = 0;
}
static WCHAR *MakeChildCommand(int argc, WCHAR **argv)
{
size_t capacity = 4, pos = 0;
WCHAR *command;
int i;
for (i = 0; i < argc; ++i) capacity += wcslen(argv[i]) * 2 + 5;
command = (WCHAR *)calloc(capacity, sizeof(WCHAR));
if (!command) return NULL;
for (i = 0; i < argc; ++i) {
if (!argv[i] || !argv[i][0]) continue;
if (pos) command[pos++] = L' ';
AddQuoted(command, argv[i], &pos);
}
return command;
}
static int RunChild(WCHAR *command)
{
PROCESS_INFORMATION pi = {0};
STARTUPINFOW si = {0};
DWORD status = 1;
si.cb = sizeof(si);
if (!CreateProcessW(NULL, command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
fwprintf(stderr, L"BUILD: error: cannot launch build (%lu)\n", GetLastError());
return 1;
}
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &status);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return (int)status;
}
/* Return -1 for a normal build; otherwise supervise -w until Ctrl+C. */
int TryWatchMode(void)
{
int argc, i, watch = 0;
WCHAR **argv = CommandLineToArgvW(GetCommandLineW(), &argc);
WCHAR *command;
struct WatchState state = {0};
HANDLE worker;
DWORD threadId;
if (!argv) return -1;
for (i = 1; i < argc; ++i) {
WCHAR *arg = argv[i];
size_t j, k;
if (arg[0] != L'-' && arg[0] != L'/') continue;
if (!_wcsicmp(arg + 1, L"why")) continue;
/* Keep whole-word and target switches unchanged. */
if (wcslen(arg) > 1 && wcschr(L"aAcCnN", arg[1]) &&
(wcslen(arg) > 3)) continue;
for (j = 1, k = 1; arg[j]; ++j) {
if (arg[j] == L'w' || arg[j] == L'W') {
watch = 1;
} else {
arg[k++] = arg[j];
}
}
arg[k] = 0;
if (k == 1) argv[i][0] = 0;
}
if (!watch) { LocalFree(argv); return -1; }
command = MakeChildCommand(argc, argv);
LocalFree(argv);
if (!command) return 1;
state.changed = CreateEventW(NULL, FALSE, FALSE, NULL);
state.directory = CreateFileW(L".", FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (!state.changed || state.directory == INVALID_HANDLE_VALUE) {
fprintf(stderr, "BUILD: error: unable to watch project directory\n");
free(command);
return 1;
}
worker = CreateThread(NULL, 0, WatchThread, &state, 0, &threadId);
if (!worker) {
fprintf(stderr, "BUILD: error: unable to start watcher\n");
free(command);
return 1;
}
for (;;) {
LONG before = InterlockedCompareExchange(&state.generation, 0, 0);
RunChild(command);
if (before == InterlockedCompareExchange(&state.generation, 0, 0)) {
printf("BUILD: Watching...\n");
fflush(stdout);
{
HANDLE signals[2] = { state.changed, worker };
if (WaitForMultipleObjects(2, signals, FALSE, INFINITE) != WAIT_OBJECT_0) {
fprintf(stderr, "BUILD: error: watcher stopped\n");
free(command);
return 1;
}
}
}
Sleep(200);
/* Drain coalesced notifications before next build. */
ResetEvent(state.changed);
}
}