Files
ViperC/copyself/App/main.vp
T
TermiNexus 782b4d63ff feat(build): 增加 --build-self 与 copyself
- 新增 copyself:延迟后删除目标并复制文件/目录
- ViperC 新增 --build-self:自举到 __vpcbuild_2__ 后由 copyself 替换 __vpcbuild__
- 同步当前 ViperC 工作区已有的 Gen/Diag/Stub 改动
- .gitignore 忽略 __vpcbuild_* 与 _sha1_map.txt
2026-10-26 02:09:45 +08:00

349 lines
13 KiB
Plaintext

from stdint import *
import stdio
import stdlib
import string
import memhub
import os
import os._win32
import os._posix
import os.path
import win32.win32base
import win32.win32file
import win32.win32console
# copyself waits for a delay, then copies a file or a directory.
# Directory source copies its contents into the destination directory.
define CODE_PAGE = 65001
define POOL_SIZE = 268435456
define MAX_DELAY_SECONDS = 2147483
define ERROR_NO_MORE_FILES = 18
define FILE_ATTRIBUTE_REPARSE_POINT = 1024
define INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
def _IsDigits(s: str) -> bool:
if s is None or s[0] == 0:
return False
i: sizet = 0
while s[i] != 0:
if s[i] < '0' or s[i] > '9':
return False
i += 1
return True
def _Lower(ch: char) -> char:
if ch >= 'A' and ch <= 'Z':
return char(int(ch) - int('A') + int('a'))
return ch
def _SamePrefix(a: str, b: str) -> bool:
alen: sizet = len(a)
if len(b) < alen:
return False
i: sizet = 0
while i < alen:
if _Lower(a[i]) != _Lower(b[i]):
return False
i += 1
return True
def _SamePath(a: str, b: str) -> bool:
if len(a) != len(b):
return False
return _SamePrefix(a, b)
def _Absolute(path: str) -> str:
if os.path.isabs(path):
return os.path.normpath(path)
cwd: str = os.getcwd()
if cwd is None:
return os.path.normpath(path)
return os.path.normpath(os.path.join(cwd, path))
def _IsSubPath(parent: str, child: str) -> bool:
plen: sizet = len(parent)
clen: sizet = len(child)
if plen == 0 or clen <= plen:
return False
if not _SamePrefix(parent, child):
return False
last: char = parent[plen - 1]
if last == '\\' or last == '/':
return True
ch: char = child[plen]
return ch == '\\' or ch == '/'
def _EnsureDir(path: str) -> int:
if path is None or path[0] == 0:
stdio.printf("copyself: cannot create an empty directory path\n")
return -1
if len(path) == 2 and path[1] == ':':
return 0
if os.isdir(path):
return 0
if os.exists(path):
stdio.printf("copyself: cannot create directory '%s': a file with the same name exists\n", path)
return -1
parent: str = os.path.dirname(path)
if parent is not None and len(parent) > 0 and not _SamePath(parent, path):
if not os.isdir(parent):
if _EnsureDir(parent) != 0:
return -1
if os.mkdir(path) != 0:
if os.isdir(path):
return 0
err: uint32 = win32.win32base.GetLastError()
stdio.printf("copyself: failed to create directory '%s' (Win32 error %d)\n", path, int(err))
return -1
return 0
def _CopyFile(src: str, dst: str) -> int:
if win32.win32file.CopyFileA(src, dst, 0) == 0:
err: uint32 = win32.win32base.GetLastError()
stdio.printf("copyself: failed to copy file '%s' -> '%s' (Win32 error %d)\n", src, dst, int(err))
return -1
return 0
def _CopyDirContents(src: str, dst: str) -> int:
pattern: str = os.path.join(src, "*")
if pattern is None:
stdio.printf("copyself: failed to build the search pattern for '%s'\n", src)
return -1
find_data: win32.win32file.WIN32_FIND_DATAA = win32.win32file.WIN32_FIND_DATAA()
handle: win32.win32base.HANDLE = win32.win32file.FindFirstFileA(pattern, &find_data)
if handle == win32.win32base.INVALID_HANDLE_VALUE:
err: uint32 = win32.win32base.GetLastError()
stdio.printf("copyself: failed to list directory '%s' (Win32 error %d)\n", src, int(err))
return -1
rc: int = 0
while True:
name: str = find_data.cFileName
skip: bool = False
if name[0] == '.':
if name[1] == 0:
skip = True
elif name[1] == '.' and name[2] == 0:
skip = True
if not skip:
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0:
stdio.printf("copyself: reparse point '%s' under '%s' is not supported\n", name, src)
rc = -1
break
child_src: str = os.path.join(src, name)
child_dst: str = os.path.join(dst, name)
if child_src is None or child_dst is None:
stdio.printf("copyself: failed to build child paths for '%s'\n", name)
rc = -1
break
if _CopyEntry(child_src, child_dst) != 0:
rc = -1
break
if win32.win32file.FindNextFileA(handle, &find_data) == 0:
err2: uint32 = win32.win32base.GetLastError()
if int(err2) != ERROR_NO_MORE_FILES:
stdio.printf("copyself: failed while listing directory '%s' (Win32 error %d)\n", src, int(err2))
rc = -1
break
win32.win32file.FindClose(handle)
return rc
def _RemoveTree(path: str) -> int:
pattern: str = os.path.join(path, "*")
if pattern is None:
stdio.printf("copyself: failed to build the search pattern for '%s'\n", path)
return -1
find_data: win32.win32file.WIN32_FIND_DATAA = win32.win32file.WIN32_FIND_DATAA()
handle: win32.win32base.HANDLE = win32.win32file.FindFirstFileA(pattern, &find_data)
if handle == win32.win32base.INVALID_HANDLE_VALUE:
err: ULONG = win32.win32base.GetLastError()
stdio.printf("copyself: failed to list directory '%s' (Win32 error %d)\n", path, int(err))
return -1
rc: int = 0
while True:
name: str = find_data.cFileName
skip: bool = False
if name[0] == '.':
if name[1] == 0:
skip = True
elif name[1] == '.' and name[2] == 0:
skip = True
if not skip:
child: str = os.path.join(path, name)
if child is None:
stdio.printf("copyself: failed to build the child path for '%s'\n", name)
rc = -1
break
attrs: ULONG = find_data.dwFileAttributes
if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0:
if (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
if win32.win32file.RemoveDirectoryA(child) == 0:
err_jd: ULONG = win32.win32base.GetLastError()
stdio.printf("copyself: failed to remove junction '%s' (Win32 error %d)\n", child, int(err_jd))
rc = -1
break
else:
if win32.win32file.DeleteFileA(child) == 0:
err_jf: ULONG = win32.win32base.GetLastError()
stdio.printf("copyself: failed to remove '%s' (Win32 error %d)\n", child, int(err_jf))
rc = -1
break
elif (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
if _RemoveTree(child) != 0:
rc = -1
break
else:
if win32.win32file.DeleteFileA(child) == 0:
err_f: ULONG = win32.win32base.GetLastError()
stdio.printf("copyself: failed to remove file '%s' (Win32 error %d)\n", child, int(err_f))
rc = -1
break
if win32.win32file.FindNextFileA(handle, &find_data) == 0:
err_next: ULONG = win32.win32base.GetLastError()
if int(err_next) != ERROR_NO_MORE_FILES:
stdio.printf("copyself: failed while listing directory '%s' (Win32 error %d)\n", path, int(err_next))
rc = -1
break
win32.win32file.FindClose(handle)
if rc != 0:
return -1
if win32.win32file.RemoveDirectoryA(path) == 0:
err_rmdir: ULONG = win32.win32base.GetLastError()
stdio.printf("copyself: failed to remove directory '%s' (Win32 error %d)\n", path, int(err_rmdir))
return -1
return 0
def _DeletePath(path: str) -> int:
if not os.exists(path):
return 0
attrs: ULONG = win32.win32file.GetFileAttributesA(path)
if attrs == INVALID_FILE_ATTRIBUTES:
err_attr: ULONG = win32.win32base.GetLastError()
stdio.printf("copyself: failed to query '%s' (Win32 error %d)\n", path, int(err_attr))
return -1
if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0:
if (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
if win32.win32file.RemoveDirectoryA(path) == 0:
err_jd: ULONG = win32.win32base.GetLastError()
stdio.printf("copyself: failed to remove junction '%s' (Win32 error %d)\n", path, int(err_jd))
return -1
else:
if win32.win32file.DeleteFileA(path) == 0:
err_jf: ULONG = win32.win32base.GetLastError()
stdio.printf("copyself: failed to remove '%s' (Win32 error %d)\n", path, int(err_jf))
return -1
return 0
if (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
return _RemoveTree(path)
if win32.win32file.DeleteFileA(path) == 0:
err_f: ULONG = win32.win32base.GetLastError()
stdio.printf("copyself: failed to remove file '%s' (Win32 error %d)\n", path, int(err_f))
return -1
return 0
def _CopyEntry(src: str, dst: str) -> int:
if os.isdir(src):
if os.exists(dst):
if not os.isdir(dst):
stdio.printf("copyself: source '%s' is a directory but destination '%s' is a file\n", src, dst)
return -1
if _DeletePath(dst) != 0:
return -1
if _EnsureDir(dst) != 0:
return -1
return _CopyDirContents(src, dst)
if os.isfile(src):
if os.isdir(dst):
stdio.printf("copyself: source '%s' is a file but destination '%s' is a directory\n", src, dst)
return -1
if os.exists(dst):
if _DeletePath(dst) != 0:
return -1
parent: str = os.path.dirname(dst)
if parent is not None and len(parent) > 0 and not os.isdir(parent):
if _EnsureDir(parent) != 0:
return -1
return _CopyFile(src, dst)
stdio.printf("copyself: source '%s' is neither a regular file nor a directory\n", src)
return -1
def main(argc: int, argv: []str) -> int:
win32.win32console.SetConsoleOutputCP(CODE_PAGE)
win32.win32console.SetConsoleCP(CODE_PAGE)
if argc != 4:
stdio.printf("usage: copyself <seconds> <source> <destination>\n")
return 1
if argv[2][0] == 0 or argv[3][0] == 0:
stdio.printf("copyself: source and destination must not be empty\n")
return 1
if not _IsDigits(argv[1]):
stdio.printf("copyself: invalid delay '%s' (expected a non-negative integer)\n", argv[1])
return 1
seconds: int64 = string.atoll(argv[1])
if seconds < 0 or seconds > int64(MAX_DELAY_SECONDS):
stdio.printf("copyself: delay '%s' is out of range (0..%d seconds)\n", argv[1], MAX_DELAY_SECONDS)
return 1
arena: bytes = stdlib.malloc(sizet(POOL_SIZE))
if arena is None:
stdio.printf("copyself: failed to allocate the memory pool\n")
return 1
mb: memhub.MemBuddy* = memhub.MemBuddy(arena, sizet(POOL_SIZE), 0, 0)
if mb is None:
stdio.printf("copyself: failed to initialize the memory pool\n")
return 1
os._mbuddy = mb
os._win32._mbuddy = mb
os._posix._mbuddy = mb
os.path._mbuddy = mb
src: str = _Absolute(argv[2])
dst: str = _Absolute(argv[3])
if src is None or dst is None:
stdio.printf("copyself: failed to normalize the source or destination path\n")
return 1
if not os.exists(src):
stdio.printf("copyself: source '%s' does not exist\n", src)
return 1
if _SamePath(src, dst):
stdio.printf("copyself: source and destination are the same path: '%s'\n", src)
return 1
if os.isdir(src):
if _IsSubPath(src, dst):
stdio.printf("copyself: destination '%s' is inside source directory '%s'\n", dst, src)
return 1
if _IsSubPath(dst, src):
stdio.printf("copyself: source directory '%s' is inside destination '%s'\n", src, dst)
return 1
if os.exists(dst) and not os.isdir(dst):
stdio.printf("copyself: source '%s' is a directory but destination '%s' is a file\n", src, dst)
return 1
elif os.isfile(src):
if os.isdir(dst):
stdio.printf("copyself: source '%s' is a file but destination '%s' is a directory\n", src, dst)
return 1
else:
stdio.printf("copyself: source '%s' is neither a regular file nor a directory\n", src)
return 1
stdio.printf("copyself: waiting %d second(s) before copying\n", int(seconds))
win32.win32base.Sleep(ULONG(seconds * 1000))
stdio.printf("copyself: copying '%s' -> '%s'\n", src, dst)
rc: int = _CopyEntry(src, dst)
if rc == 0:
stdio.printf("copyself: done\n")
return 0
return 1