Skip to content
This repository was archived by the owner on Feb 4, 2020. It is now read-only.
Open
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from __future__ import print_function
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of the clcache script code now requires Python 3.3 or newer. I think imposing the same requirement here would be consistent, and it would simplify things abit (e.g. this import is not needed).


import argparse
import glob
import os
import sys
import textwrap
import subprocess


def ensure_dir(path):
if os.path.exists(path):
return

os.mkdir(path)
return path


def traverse_toolsets(f):
msbuild_dir = r'C:\Program Files (x86)\MSBuild'
toolsets = glob.glob(r'{}\Microsoft.Cpp\v4.0\*\Platforms\*\PlatformToolsets\*'.format(msbuild_dir))
for toolset in toolsets:
f(toolset)


def clcache_props_path(toolset):
return os.path.join(toolset, 'ImportAfter', 'Clcache.props')


def generate_props_content(clcache_dir):
return textwrap.dedent('''
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ExecutablePath>{clcache_dir};$(ExecutablePath)</ExecutablePath>
</PropertyGroup>
</Project>''').format(clcache_dir=clcache_dir)[1:]
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some documentation on how these .props files work? I never saw this before.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just found in MSBuild\Microsoft.Cpp\v4.0\V140\Platforms\x64\PlatformToolsets\v140\Toolset.props this lines:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildThisFileDirectory)ImportBefore\*.props" Condition="Exists('$(MSBuildThisFileDirectory)ImportBefore')" />
  ...
  <Import Project="$(MSBuildThisFileDirectory)ImportAfter\*.props" Condition="Exists('$(MSBuildThisFileDirectory)ImportAfter')" />
</Project>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First my idea was to create own toolset like clang-cl. I read existing toolset files and found this extension point.



def check_call_quiet(*args, **kwargs):
with open(os.devnull, 'w') as devnull:
kwargs['stderr'] = devnull
kwargs['stdout'] = devnull
subprocess.check_call(*args, **kwargs)


def set_system_variable(var, value):
if value:
check_call_quiet(['setx', '-m', var, value])
else:
reg_path = r'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it also be sufficient to affect just the current user, i.e. use HKCU instead of HKLM?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likely. Thanks, I will try it =)

try:
check_call_quiet(['reg', 'query', reg_path, '/V', var])
except subprocess.CalledProcessError:
# Variable doesn't exists
pass
else:
check_call_quiet(['reg', 'delete', reg_path, '/F', '/V', var])


def install(exe, cache_dir):
def f(toolset):
clcache_props = clcache_props_path(toolset)
ensure_dir(os.path.dirname(clcache_props))
with open(clcache_props, 'w') as f:
f.write(generate_props_content(os.path.dirname(exe)))

traverse_toolsets(f)
set_system_variable('CLCACHE_DIR', cache_dir)
set_system_variable('CL', '/MP4')
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this script should change the CL variable behind the scenes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is doubtful place.
The thing is that in out projects /Fo always point to directory, not to file. And without setting CL variable we always has CallsWithMultipleSourceFiles.
Since I wrote the script in the first place for my team and was not sure of its usefulness for the clcache community, I left this line. But it can easily be removed if need be to get into the upstream.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... looks like my experience about CallsWithMultipleSourceFiles is outdated now.



def uninstall():
def f(toolset):
clcache_props = clcache_props_path(toolset)
if os.path.exists(clcache_props):
os.remove(clcache_props)

traverse_toolsets(f)
set_system_variable('CLCACHE_DIR', None)
set_system_variable('CL', None)


def main(args=sys.argv[1:]):
clcache_default_exe = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'dist',
'cl.exe')
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the executable have to be called cl.exe such that Visual Studio accepts it? Otherwise, I'd use clcache.exe here since that's the file name you get when you follow the documented way to build an executable, namely by running

pyinstaller --onefile clcache.exe

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to use $(ClToolExe) and $(ClToolPath) instead of $(ExecutablePath) in Clcache.props. Would be great if it would works.


def cl_exe_type(exe):
if not os.path.isfile(exe):
raise argparse.ArgumentTypeError('{} is not file'.format(exe))
if os.path.basename(exe) != 'cl.exe':
raise argparse.ArgumentTypeError('clcache executable must be named cl.exe')
return exe

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='action')
parser_install = subparsers.add_parser('install')
parser_install.add_argument('--exe', type=cl_exe_type, default=clcache_default_exe)
parser_install.add_argument('--cache-dir')
parser_uninstall = subparsers.add_parser('uninstall')
args = parser.parse_args(args)

if args.action == 'install':
install(os.path.abspath(args.exe), os.path.abspath(args.cache_dir))
else:
uninstall()


if __name__ == '__main__':
sys.exit(main())