Skip to content

Commit

Permalink
Fix some functions for windows:
Browse files Browse the repository at this point in the history
- Workspace locking
- Console color
- ninja.build generation
- CC toolchain
  • Loading branch information
phongchen committed Sep 29, 2023
1 parent 04553e0 commit 9952a14
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 27 deletions.
10 changes: 7 additions & 3 deletions src/blade/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from __future__ import absolute_import
from __future__ import print_function

import codecs
import os
import subprocess
import sys
Expand Down Expand Up @@ -730,9 +731,12 @@ def generate_cuda_rules(self):
description='CUDA LINK SHARED ${out}')

def _builtin_command(self, builder, args=''):
cmd = ['PYTHONPATH=%s:$$PYTHONPATH' % self.blade_path]
if os.name == 'nt':
cmd = ['cmd /c set PYTHONPATH=%s:%%PYTHONPATH%%;' % self.blade_path]
else:
cmd = ['PYTHONPATH=%s:$$PYTHONPATH' % self.blade_path]
python = os.environ.get('BLADE_PYTHON_INTERPRETER') or sys.executable
cmd.append('%s -m blade.builtin_tools %s' % (python, builder))
cmd.append('"%s" -m blade.builtin_tools %s' % (python, builder))
if args:
cmd.append(args)
else:
Expand Down Expand Up @@ -789,6 +793,6 @@ def generate_build_code(self):
def generate_build_script(self):
"""Generate build script for underlying build system."""
code = self.generate_build_code()
script = open(self.script_path, 'w')
script = codecs.open(self.script_path, 'w', 'utf-8')
script.writelines(code)
script.close()
4 changes: 2 additions & 2 deletions src/blade/build_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
from blade import maven
from blade import ninja_runner
from blade import target_pattern
from blade import toolchain
from blade.binary_runner import BinaryRunner
from blade.toolchain import ToolChain
from blade.build_accelerator import BuildAccelerator
from blade.dependency_analyzer import analyze_deps
from blade.load_build_files import load_targets
Expand Down Expand Up @@ -101,7 +101,7 @@ def __init__(self,
# Indicate whether the deps list is expanded by expander or not
self.__targets_expanded = False

self.__build_toolchain = ToolChain()
self.__build_toolchain = toolchain.default()
self.build_accelerator = BuildAccelerator(self.__build_toolchain)
self.__build_jobs_num = 0

Expand Down
10 changes: 8 additions & 2 deletions src/blade/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from blade import console
from blade import constants
from blade.toolchain import BuildArchitecture
from blade.toolchain import ToolChain
from blade.toolchain import CcToolChain

# The 'version.py' is generated by the dist script, then it only exists in blade.zip
try:
Expand Down Expand Up @@ -98,6 +98,11 @@ def _check_test_options(self, options, targets):

def _check_plat_and_profile_options(self, options, targets):
"""check platform and profile options."""
arch = 'x64'
bits = 32
options.arch = arch
options.bits = bits
return
compiler_arch = self._compiler_target_arch()
arch = BuildArchitecture.get_canonical_architecture(compiler_arch)
if arch is None:
Expand Down Expand Up @@ -414,6 +419,7 @@ def _add_dump_arguments(self, parser):
group.add_argument(
'--all-tags', dest='dump_all_tags', default=False, action='store_true',
help='Dump all tags of targets in json format')

def _build_arg_parser(self):
"""Add command options, add options whthin this method."""
blade_cmd_help = 'blade <subcommand> [options...] [targets...]'
Expand Down Expand Up @@ -462,7 +468,7 @@ def _build_arg_parser(self):

def _compiler_target_arch(self):
"""Compiler(gcc) target architecture."""
arch = ToolChain.get_cc_target_arch()
arch = CcToolChain.get_cc_target_arch()
pos = arch.find('-')
if pos == -1:
console.fatal('Unknown target architecture %s from gcc.' % arch)
Expand Down
31 changes: 28 additions & 3 deletions src/blade/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import datetime
import os
import subprocess
import sys
import time

Expand All @@ -25,10 +26,34 @@
##############################################################################


# Global color enabled or not
_color_enabled = (sys.stdout.isatty() and
os.environ.get('TERM') not in ('emacs', 'dumb'))
def _windows_console_support_ansi_color():
from ctypes import byref, windll, wintypes
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
INVALID_HANDLE_VALUE = -1

handle = windll.kernel32.GetStdHandle(subprocess.STD_OUTPUT_HANDLE)
if handle == INVALID_HANDLE_VALUE:
return False

mode = wintypes.DWORD()
if not windll.kernel32.GetConsoleMode(handle, byref(mode)):
return False

if not (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING):
if windll.kernel32.SetConsoleMode(
handle,
mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0:
print('kernel32.SetConsoleMode to enable ANSI sequences failed',
file=sys.stderr)
return True

def _console_support_ansi_color():
if os.name == 'nt':
return _windows_console_support_ansi_color()
return sys.stdout.isatty() and os.environ.get('TERM') not in ('emacs', 'dumb')

# Global color enabled or not
_color_enabled = _console_support_ansi_color()
# See http://en.wikipedia.org/wiki/ANSI_escape_code
# colors

Expand Down
147 changes: 131 additions & 16 deletions src/blade/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,47 @@ def get_model_architecture(arch, bits):
return None


class ToolChain(object):
class CcToolChain(object):
"""The build platform handles and gets the platform information."""

def __init__(self):
self.cc = ''
self.cxx = ''
self.ld = ''
self.cc_version = ''
self.ar = ''

@staticmethod
def get_cc_target_arch():
"""Get the cc target architecture."""
cc = CcToolChain._get_cc_command('CC', 'gcc')
returncode, stdout, stderr = run_command(cc + ' -dumpmachine', shell=True)
if returncode == 0:
return stdout.strip()
return ''

def get_cc_commands(self):
return self.cc, self.cxx, self.ld

def get_cc(self):
return self.cc

def get_cc_version(self):
return self.cc_version

def get_ar(self):
return self.ar

def cc_is(self, vendor):
"""Is cc is used for C/C++ compilation match vendor."""
return vendor in self.cc

def filter_cc_flags(self, flag_list, language='c'):
"""Filter out the unrecognized compilation flags."""
raise NotImplementedError


class CcToolChainGcc(CcToolChain):
"""The build platform handles and gets the platform information."""

def __init__(self):
Expand Down Expand Up @@ -132,7 +172,78 @@ def _get_cc_version(self):
@staticmethod
def get_cc_target_arch():
"""Get the cc target architecture."""
cc = ToolChain._get_cc_command('CC', 'gcc')
cc = CcToolChain._get_cc_command('CC', 'gcc')
returncode, stdout, stderr = run_command(cc + ' -dumpmachine', shell=True)
if returncode == 0:
return stdout.strip()
return ''

def filter_cc_flags(self, flag_list, language='c'):
"""Filter out the unrecognized compilation flags."""
flag_list = var_to_list(flag_list)
valid_flags, unrecognized_flags = [], []

# Put compilation output into test.o instead of /dev/null
# because the command line with '--coverage' below exit
# with status 1 which makes '--coverage' unsupported
# echo "int main() { return 0; }" | gcc -o /dev/null -c -x c --coverage - > /dev/null 2>&1
fd, obj = tempfile.mkstemp('.o', 'filter_cc_flags_test')
cmd = ('export LC_ALL=C; echo "int main() { return 0; }" | '
'%s -o %s -c -x %s -Werror %s -' % (
self.cc, obj, language, ' '.join(flag_list)))
returncode, _, stderr = run_command(cmd, shell=True)

try:
# In case of error, the `.o` file will be deleted by the compiler
os.remove(obj)
except OSError:
pass
os.close(fd)

if returncode == 0:
return flag_list
for flag in flag_list:
# Example error messages:
# clang: warning: unknown warning option '-Wzzz' [-Wunknown-warning-option]
# gcc: gcc: error: unrecognized command line option '-Wxxx'
if " option '%s'" % flag in stderr:
unrecognized_flags.append(flag)
else:
valid_flags.append(flag)

if unrecognized_flags:
console.warning('config: Unrecognized %s flags: %s' % (
language, ', '.join(unrecognized_flags)))

return valid_flags


class CcToolChainMsvc(CcToolChain):
"""The build platform handles and gets the platform information."""

def __init__(self):
self.cc = 'cl.exe'
self.cxx = 'cl.exe'
self.ld = 'link.exe'
self.ar = 'lib.exe'
self.rc = 'rc.exe'
self.cc_version = self._get_cc_version()

def _get_cc_version(self):
version = ''
returncode, stdout, stderr = run_command(self.cc, shell=True)
if returncode == 0:
m = re.search('Compiler Version ([\d.]+)', stderr.strip())
if m:
version = m.group(1)
if not version:
console.fatal('Failed to obtain cc toolchain.')
return version

@staticmethod
def get_cc_target_arch():
"""Get the cc target architecture."""
cc = CcToolChain._get_cc_command('CC', 'gcc')
returncode, stdout, stderr = run_command(cc + ' -dumpmachine', shell=True)
if returncode == 0:
return stdout.strip()
Expand Down Expand Up @@ -163,32 +274,36 @@ def filter_cc_flags(self, flag_list, language='c'):
# because the command line with '--coverage' below exit
# with status 1 which makes '--coverage' unsupported
# echo "int main() { return 0; }" | gcc -o /dev/null -c -x c --coverage - > /dev/null 2>&1
fd, obj = tempfile.mkstemp('.o', 'filter_cc_flags_test')
cmd = ('export LC_ALL=C; echo "int main() { return 0; }" | '
'%s -o %s -c -x %s -Werror %s -' % (
self.cc, obj, language, ' '.join(flag_list)))
returncode, _, stderr = run_command(cmd, shell=True)

try:
# In case of error, the `.o` file will be deleted by the compiler
os.remove(obj)
except OSError:
pass
suffix = language
if suffix == 'c++':
suffix = 'cpp'
fd, src = tempfile.mkstemp('.' + suffix, 'filter_cc_flags_test')
os.write(fd, b"int main() { return 0; }\n")
os.close(fd)

if returncode == 0:
return flag_list
for flag in flag_list:
# Example error messages:
# clang: warning: unknown warning option '-Wzzz' [-Wunknown-warning-option]
# gcc: gcc: error: unrecognized command line option '-Wxxx'
if " option '%s'" % flag in stderr:
cmd = ('"%s" /nologo /FoNUL /c /WX %s "%s"' % (self.cc, flag, src))
returncode, stdout, stderr = run_command(cmd, shell=True)
message = stdout + stderr
if "'%s'" % flag in message:
unrecognized_flags.append(flag)
else:
valid_flags.append(flag)
try:
# In case of error, the `.o` file will be deleted by the compiler
os.remove(src)
except OSError:
pass

if unrecognized_flags:
console.warning('config: Unrecognized %s flags: %s' % (
language, ', '.join(unrecognized_flags)))

return valid_flags


def default():
return CcToolChainMsvc()
13 changes: 12 additions & 1 deletion src/blade/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
except ImportError:
fcntl = None

try:
import msvcrt
except ImportError:
msvcrt = None

_IN_PY3 = sys.version_info[0] == 3


Expand Down Expand Up @@ -77,13 +82,17 @@ def md5sum(obj):
def lock_file(filename):
"""lock file."""
try:
fd = os.open(filename, os.O_CREAT | os.O_RDWR | os.O_EXCL)
fd = os.open(filename, os.O_CREAT | os.O_RDWR)
if fcntl:
old_fd_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, old_fd_flags | fcntl.FD_CLOEXEC)
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
elif msvcrt:
msvcrt.locking(fd, msvcrt.LK_NBLCK, os.stat(fd).st_size)
return fd, 0
except IOError as ex_value:
if msvcrt: # msvcrt did't set errno correctly
return -1, errno.EAGAIN
return -1, ex_value.errno


Expand All @@ -92,6 +101,8 @@ def unlock_file(fd):
try:
if fcntl:
fcntl.flock(fd, fcntl.LOCK_UN)
elif msvcrt:
msvcrt.locking(fd, msvcrt.LK_UNLCK, os.stat(fd).st_size)
os.close(fd)
except IOError:
pass
Expand Down

0 comments on commit 9952a14

Please sign in to comment.