Added Visual Studio project generation.
This commit is contained in:
parent
8770bd97dc
commit
af53bf6084
260
SConscript
260
SConscript
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
import enum
|
import enum
|
||||||
import glob
|
import glob
|
||||||
import inspect
|
import inspect
|
||||||
@ -12,8 +12,10 @@ import psutil
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from typing import Any
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from SCons.Node import Node
|
||||||
|
|
||||||
class TargetType(enum.Enum):
|
class TargetType(enum.Enum):
|
||||||
PROGRAM = 0
|
PROGRAM = 0
|
||||||
@ -41,6 +43,14 @@ class _Dependency:
|
|||||||
depdeps: list = []
|
depdeps: list = []
|
||||||
cook_result: dict = {}
|
cook_result: dict = {}
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _Module:
|
||||||
|
name: str
|
||||||
|
folder: str
|
||||||
|
description: str
|
||||||
|
cxx_namespace: str
|
||||||
|
targets: list['_Target'] = field(default_factory=list)
|
||||||
|
|
||||||
class _Target:
|
class _Target:
|
||||||
name: str
|
name: str
|
||||||
target_type: TargetType
|
target_type: TargetType
|
||||||
@ -49,12 +59,7 @@ class _Target:
|
|||||||
kwargs: dict = {}
|
kwargs: dict = {}
|
||||||
dependencies: list = []
|
dependencies: list = []
|
||||||
target = None
|
target = None
|
||||||
|
module: _Module = None
|
||||||
@dataclass
|
|
||||||
class _Module:
|
|
||||||
name: str
|
|
||||||
description: str
|
|
||||||
cxx_namespace: str
|
|
||||||
|
|
||||||
def _find_recipe(env: Environment, recipe_name: str):
|
def _find_recipe(env: Environment, recipe_name: str):
|
||||||
if recipe_name in env['SPP_RECIPES']:
|
if recipe_name in env['SPP_RECIPES']:
|
||||||
@ -98,13 +103,17 @@ def _cook(env: Environment, recipe_name: str):
|
|||||||
|
|
||||||
def _normalize_module_path(env: Environment, path: str) -> str:
|
def _normalize_module_path(env: Environment, path: str) -> str:
|
||||||
module_root = env.Dir('#/private').abspath
|
module_root = env.Dir('#/private').abspath
|
||||||
|
try:
|
||||||
return os.path.relpath(path, module_root)
|
return os.path.relpath(path, module_root)
|
||||||
|
except ValueError: # may be thrown on Windows if the module is on a different drive than the project
|
||||||
|
return os.path.normpath(path) # just use the absolute path then
|
||||||
|
|
||||||
def _module(env: Environment, file: str):
|
def _module(env: Environment, file: str):
|
||||||
folder = _normalize_module_path(env, env.File(file).dir.abspath)
|
folder = _normalize_module_path(env, env.File(file).dir.abspath)
|
||||||
dirname = os.path.basename(folder)
|
dirname = os.path.basename(folder)
|
||||||
env.Append(SPP_MODULES = {folder: _Module(
|
env.Append(SPP_MODULES = {folder: _Module(
|
||||||
name=dirname,
|
name=dirname,
|
||||||
|
folder=folder,
|
||||||
description='',
|
description='',
|
||||||
cxx_namespace=dirname
|
cxx_namespace=dirname
|
||||||
)})
|
)})
|
||||||
@ -454,6 +463,13 @@ def _wrap_builder(builder, target_type: TargetType):
|
|||||||
target.args = args
|
target.args = args
|
||||||
target.kwargs = kwargs
|
target.kwargs = kwargs
|
||||||
target.dependencies = target_dependencies
|
target.dependencies = target_dependencies
|
||||||
|
module_folder = _normalize_module_path(env, env.Dir('.').abspath)
|
||||||
|
module = env['SPP_MODULES'].get(module_folder)
|
||||||
|
if module is None:
|
||||||
|
env.Warn(f'No module config found for target {target.name} at {module_folder}')
|
||||||
|
else:
|
||||||
|
target.module = module
|
||||||
|
module.targets.append(target)
|
||||||
env.Append(SPP_TARGETS = [target])
|
env.Append(SPP_TARGETS = [target])
|
||||||
if not target.dependencies:
|
if not target.dependencies:
|
||||||
_build_target(target)
|
_build_target(target)
|
||||||
@ -582,7 +598,8 @@ def _generate_project(project_type: str) -> None:
|
|||||||
|
|
||||||
source_folder, target_folder = {
|
source_folder, target_folder = {
|
||||||
'clion': (os.path.join(_spp_dir.abspath, 'util', 'clion_project_template'), Dir('#.idea').abspath),
|
'clion': (os.path.join(_spp_dir.abspath, 'util', 'clion_project_template'), Dir('#.idea').abspath),
|
||||||
'vscode': (os.path.join(_spp_dir.abspath, 'util', 'vscode_project_template'), Dir('#.vscode').abspath)
|
'vscode': (os.path.join(_spp_dir.abspath, 'util', 'vscode_project_template'), Dir('#.vscode').abspath),
|
||||||
|
'vs': (os.path.join(_spp_dir.abspath, 'util', 'vs_project_template'), Dir('#').abspath)
|
||||||
}.get(project_type, (None, None))
|
}.get(project_type, (None, None))
|
||||||
if not source_folder:
|
if not source_folder:
|
||||||
_error(None, 'Invalid project type option.')
|
_error(None, 'Invalid project type option.')
|
||||||
@ -597,15 +614,18 @@ def _generate_project(project_type: str) -> None:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'Error loading UUID cache: {e}')
|
print(f'Error loading UUID cache: {e}')
|
||||||
|
|
||||||
def _generate_uuid(name: str = '') -> str:
|
def _generate_uuid(name: str = '', ms_style: bool = False) -> str:
|
||||||
nonlocal save_uuid_cache
|
nonlocal save_uuid_cache
|
||||||
if name and name in uuid_cache:
|
if name and name in uuid_cache:
|
||||||
return uuid_cache[name]
|
result = uuid_cache[name]
|
||||||
new_uuid = str(uuid.uuid4())
|
else:
|
||||||
|
result = str(uuid.uuid4())
|
||||||
if name:
|
if name:
|
||||||
uuid_cache[name] = new_uuid
|
uuid_cache[name] = result
|
||||||
save_uuid_cache = True
|
save_uuid_cache = True
|
||||||
return new_uuid
|
if ms_style:
|
||||||
|
return f'{{{result.upper()}}}'
|
||||||
|
return result
|
||||||
|
|
||||||
root_path = pathlib.Path(env.Dir('#').abspath)
|
root_path = pathlib.Path(env.Dir('#').abspath)
|
||||||
def _get_executables() -> list:
|
def _get_executables() -> list:
|
||||||
@ -619,7 +639,10 @@ def _generate_project(project_type: str) -> None:
|
|||||||
return str(exe_path)
|
return str(exe_path)
|
||||||
result.append({
|
result.append({
|
||||||
'name': target.name,
|
'name': target.name,
|
||||||
'filename': _exe_path
|
'filename': _exe_path,
|
||||||
|
'target': target,
|
||||||
|
'type': 'executable',
|
||||||
|
'module': target.module
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
def _get_libraries() -> list:
|
def _get_libraries() -> list:
|
||||||
@ -633,7 +656,10 @@ def _generate_project(project_type: str) -> None:
|
|||||||
return str(lib_path)
|
return str(lib_path)
|
||||||
result.append({
|
result.append({
|
||||||
'name': target.name,
|
'name': target.name,
|
||||||
'filename': _lib_path
|
'filename': _lib_path,
|
||||||
|
'target': target,
|
||||||
|
'type': 'static_library',
|
||||||
|
'module': target.module
|
||||||
})
|
})
|
||||||
elif target.target_type == TargetType.SHARED_LIBRARY:
|
elif target.target_type == TargetType.SHARED_LIBRARY:
|
||||||
trgt = _target_entry(target.kwargs['target'])
|
trgt = _target_entry(target.kwargs['target'])
|
||||||
@ -643,40 +669,153 @@ def _generate_project(project_type: str) -> None:
|
|||||||
return str(lib_path)
|
return str(lib_path)
|
||||||
result.append({
|
result.append({
|
||||||
'name': target.name,
|
'name': target.name,
|
||||||
'filename': _lib_path
|
'filename': _lib_path,
|
||||||
|
'target': target,
|
||||||
|
'type': 'static_library',
|
||||||
|
'module': target.module
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
def _get_modules() -> list:
|
||||||
|
result = []
|
||||||
|
for folder, config in env['SPP_MODULES'].items():
|
||||||
|
result.append({
|
||||||
|
'name': config.name,
|
||||||
|
'private_folder': os.path.join('private', folder),
|
||||||
|
'public_folder': os.path.join('public', folder),
|
||||||
|
'description': config.description,
|
||||||
|
'cxx_namespace': config.cxx_namespace
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
def _escape_path(input: str) -> str:
|
def _escape_path(input: str) -> str:
|
||||||
return input.replace('\\', '\\\\')
|
return input.replace('\\', '\\\\')
|
||||||
|
|
||||||
|
def _strip_path_prefix(path: str, skip_eles: int) -> str:
|
||||||
|
for _ in range(skip_eles):
|
||||||
|
pos = path.find(os.sep)
|
||||||
|
if pos < 0:
|
||||||
|
return ''
|
||||||
|
path = path[pos+1:]
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def _folder_list(file_list: list[str], skip_eles: int = 0) -> list[str]:
|
||||||
|
result = {}
|
||||||
|
for file in file_list:
|
||||||
|
folder = os.path.dirname(file)
|
||||||
|
folder = _strip_path_prefix(folder, skip_eles)
|
||||||
|
if folder == '':
|
||||||
|
continue
|
||||||
|
while True:
|
||||||
|
result[folder] = True
|
||||||
|
# also add all parents
|
||||||
|
sep_pos = folder.rfind(os.sep)
|
||||||
|
if sep_pos < 0:
|
||||||
|
break
|
||||||
|
folder = folder[0:sep_pos]
|
||||||
|
return list(result.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def _get_sources(target_dict: dict) -> list[str]:
|
||||||
|
target : _Target = target_dict['target']
|
||||||
|
sources = target.kwargs.get('source')
|
||||||
|
return [str(pathlib.Path(source.abspath).relative_to(root_path)) for source in sources]
|
||||||
|
|
||||||
|
def _get_headers(folder: str) -> list[str]:
|
||||||
|
result = []
|
||||||
|
for root, _, files in os.walk(folder):
|
||||||
|
for file in files:
|
||||||
|
_, ext = os.path.splitext(file)
|
||||||
|
if ext in ('.h', '.hpp', '.inl', '.hxx'):
|
||||||
|
result.append(os.path.join(root, file))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _get_target_property(build_type: str, target: str, path: str) -> Any:
|
||||||
|
import subprocess
|
||||||
|
output = subprocess.check_output((shutil.which('scons'), '--silent', f'--build_type={build_type}', '--dump=targets', '--dump_format=json', f'--dump_path={target}/{path}'), text=True).strip()
|
||||||
|
return json.loads(output)
|
||||||
|
|
||||||
|
|
||||||
|
executables = _get_executables()
|
||||||
|
libraries = _get_libraries()
|
||||||
|
modules = _get_modules()
|
||||||
|
|
||||||
jinja_env = jinja2.Environment()
|
jinja_env = jinja2.Environment()
|
||||||
jinja_env.globals['generate_uuid'] = _generate_uuid
|
jinja_env.globals['generate_uuid'] = _generate_uuid
|
||||||
|
jinja_env.globals['get_sources'] = _get_sources
|
||||||
|
jinja_env.globals['get_headers'] = _get_headers
|
||||||
|
jinja_env.globals['get_target_property'] = _get_target_property
|
||||||
jinja_env.globals['project'] = {
|
jinja_env.globals['project'] = {
|
||||||
'name': env.Dir('#').name,
|
'name': env.Dir('#').name,
|
||||||
'executables': _get_executables(),
|
'executables': executables,
|
||||||
'libraries': _get_libraries(),
|
'libraries': libraries,
|
||||||
'build_types': ['debug', 'release_debug', 'release', 'profile']
|
'modules': modules,
|
||||||
|
'build_types': ['debug', 'release_debug', 'release', 'profile'],
|
||||||
|
'cxx_standard': env['CXX_STANDARD']
|
||||||
}
|
}
|
||||||
jinja_env.globals['scons_exe'] = shutil.which('scons')
|
jinja_env.globals['scons_exe'] = shutil.which('scons')
|
||||||
jinja_env.globals['nproc'] = multiprocessing.cpu_count()
|
jinja_env.globals['nproc'] = multiprocessing.cpu_count()
|
||||||
|
|
||||||
jinja_env.filters['escape_path'] = _escape_path
|
jinja_env.filters['escape_path'] = _escape_path
|
||||||
|
jinja_env.filters['strip_path_prefix'] = _strip_path_prefix
|
||||||
|
jinja_env.filters['folder_list'] = _folder_list
|
||||||
|
jinja_env.filters['basename'] = os.path.basename
|
||||||
|
jinja_env.filters['dirname'] = os.path.dirname
|
||||||
|
|
||||||
source_path = pathlib.Path(source_folder)
|
source_path = pathlib.Path(source_folder)
|
||||||
target_path = pathlib.Path(target_folder)
|
target_path = pathlib.Path(target_folder)
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
config_file = source_path / 'template.json'
|
||||||
|
if config_file.exists():
|
||||||
|
with config_file.open('r') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
files_config = config.get('files', {})
|
||||||
|
|
||||||
for source_file in source_path.rglob('*'):
|
for source_file in source_path.rglob('*'):
|
||||||
if source_file.is_file():
|
if source_file == config_file:
|
||||||
target_file = target_path / (source_file.relative_to(source_path))
|
|
||||||
target_file.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
if source_file.suffix != '.jinja':
|
|
||||||
shutil.copyfile(source_file, target_file)
|
|
||||||
continue
|
continue
|
||||||
with source_file.open('r') as f:
|
if not source_file.is_file():
|
||||||
templ = jinja_env.from_string(f.read())
|
continue
|
||||||
|
source_file_relative = source_file.relative_to(source_path)
|
||||||
|
file_config = files_config.get(str(source_file_relative).replace('\\', '/'), {})
|
||||||
|
one_per = file_config.get('one_per', 'project')
|
||||||
|
|
||||||
|
def generate_file_once() -> None:
|
||||||
|
is_jinja = (source_file.suffix == '.jinja')
|
||||||
|
if 'rename_to' in file_config:
|
||||||
|
new_filename = jinja_env.from_string(file_config['rename_to']).render()
|
||||||
|
target_file = target_path / new_filename
|
||||||
|
else:
|
||||||
|
target_file = target_path / source_file_relative
|
||||||
|
if is_jinja:
|
||||||
target_file = target_file.with_suffix('')
|
target_file = target_file.with_suffix('')
|
||||||
|
target_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
if not is_jinja:
|
||||||
|
shutil.copyfile(source_file, target_file)
|
||||||
|
return
|
||||||
|
with source_file.open('r') as f:
|
||||||
|
try:
|
||||||
|
templ = jinja_env.from_string(f.read())
|
||||||
|
except jinja2.TemplateSyntaxError as e:
|
||||||
|
e.filename = str(source_file)
|
||||||
|
raise e
|
||||||
with target_file.open('w') as f:
|
with target_file.open('w') as f:
|
||||||
f.write(templ.render())
|
f.write(templ.render())
|
||||||
|
try:
|
||||||
|
if one_per == 'project':
|
||||||
|
generate_file_once()
|
||||||
|
elif one_per == 'target':
|
||||||
|
for executable in executables:
|
||||||
|
jinja_env.globals['target'] = executable
|
||||||
|
generate_file_once()
|
||||||
|
for library in libraries:
|
||||||
|
jinja_env.globals['target'] = library
|
||||||
|
generate_file_once()
|
||||||
|
else:
|
||||||
|
raise ValueError(f'invalid value for "one_per": {one_per}')
|
||||||
|
except jinja2.TemplateSyntaxError as e:
|
||||||
|
env.Error(f'Jinja syntax error at {e.filename}:{e.lineno}: {e.message}')
|
||||||
|
Exit(1)
|
||||||
|
|
||||||
if save_uuid_cache:
|
if save_uuid_cache:
|
||||||
try:
|
try:
|
||||||
@ -691,7 +830,8 @@ def _dump() -> None:
|
|||||||
dump_name = {
|
dump_name = {
|
||||||
'env': 'Environment',
|
'env': 'Environment',
|
||||||
'config': 'Configuration',
|
'config': 'Configuration',
|
||||||
'modules': 'Modules'
|
'modules': 'Modules',
|
||||||
|
'targets': 'Targets'
|
||||||
}[dump]
|
}[dump]
|
||||||
|
|
||||||
return '\n'.join((
|
return '\n'.join((
|
||||||
@ -703,15 +843,66 @@ def _dump() -> None:
|
|||||||
class _Encoder(json.JSONEncoder):
|
class _Encoder(json.JSONEncoder):
|
||||||
def default(self, o) -> dict:
|
def default(self, o) -> dict:
|
||||||
if isinstance(o, object):
|
if isinstance(o, object):
|
||||||
|
if hasattr(o, '__iter__'):
|
||||||
|
return list(o)
|
||||||
|
elif isinstance(o, Node):
|
||||||
|
return o.abspath
|
||||||
return o.__dict__
|
return o.__dict__
|
||||||
return super().default(o)
|
return super().default(o)
|
||||||
return json.dumps(data, cls=_Encoder)
|
return json.dumps(data, cls=_Encoder)
|
||||||
|
def _apply_path(data: Any, path: str) -> Any:
|
||||||
|
for part in path.split('/'):
|
||||||
|
if isinstance(data, dict):
|
||||||
|
if part not in data:
|
||||||
|
_error(f'Invalid path specified. No key {part} in dict {data}.')
|
||||||
|
Exit(1)
|
||||||
|
data = data[part]
|
||||||
|
elif isinstance(data, list):
|
||||||
|
try:
|
||||||
|
part = int(part)
|
||||||
|
except ValueError:
|
||||||
|
_error(f'Invalid path specified. {part} is not a valid list index.')
|
||||||
|
Exit(1)
|
||||||
|
if part < 0 or part >= len(data):
|
||||||
|
_error(f'Invalid path specified. {part} is out of list range.')
|
||||||
|
Exit(1)
|
||||||
|
data = data[part]
|
||||||
|
elif isinstance(data, object):
|
||||||
|
data = data.__dict__
|
||||||
|
if part not in data:
|
||||||
|
_error(f'Invalid path specified. No attribute {part} in object {data}.')
|
||||||
|
Exit(1)
|
||||||
|
data = data[part]
|
||||||
|
else:
|
||||||
|
_error(f'Invalid path specified. {data} has no properties.')
|
||||||
|
Exit(1)
|
||||||
|
return data
|
||||||
|
def _targets() -> dict:
|
||||||
|
result = {}
|
||||||
|
for target in env['SPP_TARGETS']:
|
||||||
|
kwargs = target.kwargs.copy()
|
||||||
|
for dependency in target.dependencies:
|
||||||
|
_inject_dependency(dependency, kwargs)
|
||||||
|
result[target.name] = {
|
||||||
|
'target_type': target.target_type.name,
|
||||||
|
'args': target.args,
|
||||||
|
# 'kwargs': kwargs, <- circular dependency here and the json encoder doesn't like that
|
||||||
|
'CPPDEFINES': kwargs.get('CPPDEFINES', env['CPPDEFINES']),
|
||||||
|
'CPPPATH': kwargs.get('CPPPATH', env['CPPPATH'])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'env': env.Dictionary,
|
'env': env.Dictionary,
|
||||||
'config': lambda: config,
|
'config': lambda: config,
|
||||||
'modules': lambda: env['SPP_MODULES']
|
'modules': lambda: env['SPP_MODULES'],
|
||||||
|
'targets': _targets
|
||||||
}[dump]()
|
}[dump]()
|
||||||
|
|
||||||
|
global dump_path
|
||||||
|
dump_path = dump_path.strip()
|
||||||
|
if dump_path != '':
|
||||||
|
data = _apply_path(data, dump_path)
|
||||||
dump_fn = {
|
dump_fn = {
|
||||||
'text': _dump_as_text,
|
'text': _dump_as_text,
|
||||||
'json': _dump_as_json
|
'json': _dump_as_json
|
||||||
@ -805,7 +996,7 @@ AddOption(
|
|||||||
'--dump',
|
'--dump',
|
||||||
dest = 'dump',
|
dest = 'dump',
|
||||||
type = 'choice',
|
type = 'choice',
|
||||||
choices = ('env', 'config', 'modules'),
|
choices = ('env', 'config', 'modules', 'targets'),
|
||||||
nargs = 1,
|
nargs = 1,
|
||||||
action = 'store'
|
action = 'store'
|
||||||
)
|
)
|
||||||
@ -820,11 +1011,19 @@ AddOption(
|
|||||||
default = 'text'
|
default = 'text'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
AddOption(
|
||||||
|
'--dump_path',
|
||||||
|
dest = 'dump_path',
|
||||||
|
nargs = 1,
|
||||||
|
action = 'store',
|
||||||
|
default = ''
|
||||||
|
)
|
||||||
|
|
||||||
AddOption(
|
AddOption(
|
||||||
'--generate_project',
|
'--generate_project',
|
||||||
dest = 'generate_project',
|
dest = 'generate_project',
|
||||||
type = 'choice',
|
type = 'choice',
|
||||||
choices = ('clion', 'vscode'),
|
choices = ('clion', 'vscode', 'vs'),
|
||||||
nargs = 1,
|
nargs = 1,
|
||||||
action = 'store'
|
action = 'store'
|
||||||
)
|
)
|
||||||
@ -841,6 +1040,7 @@ update_repositories = GetOption('update_repositories')
|
|||||||
disable_auto_update = GetOption('disable_auto_update')
|
disable_auto_update = GetOption('disable_auto_update')
|
||||||
dump = GetOption('dump')
|
dump = GetOption('dump')
|
||||||
dump_format = GetOption('dump_format')
|
dump_format = GetOption('dump_format')
|
||||||
|
dump_path = GetOption('dump_path')
|
||||||
generate_project = GetOption('generate_project')
|
generate_project = GetOption('generate_project')
|
||||||
|
|
||||||
default_CC = {
|
default_CC = {
|
||||||
|
53
contrib/vs/spp.targets
Normal file
53
contrib/vs/spp.targets
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<SolutionExt>.sln</SolutionExt>
|
||||||
|
<Language>C++</Language>
|
||||||
|
<DefaultLanguageSourceExtension>.cpp</DefaultLanguageSourceExtension>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFileName Condition="'$(TargetPath)' != ''">$([System.IO.Path]::GetFileName('$(TargetPath)'))</TargetFileName>
|
||||||
|
<TargetDir Condition="'$(TargetPath)' != ''">$([System.IO.Path]::GetDirectoryName('$(TargetPath)'))</TargetDir>
|
||||||
|
<OutputPath>$(TargetDir)</OutputPath>
|
||||||
|
<LocalDebuggerCommand Condition="'$(LocalDebuggerCommand)' == ''">$(TargetPath)</LocalDebuggerCommand>
|
||||||
|
|
||||||
|
<SConsCommandLine Condition="'$(SConsCommandLine)' == ''">scons</SConsCommandLine>
|
||||||
|
<SPPNumProcs Condition="'$(SPPNumProcs)' == ''">$([System.Environment]::ProcessorCount)</SPPNumProcs>
|
||||||
|
<SPPBuildType Condition="'$(SPPBuildType)' == ''">debug</SPPBuildType>
|
||||||
|
<SPPTargetType Condition="'$(SPPTargetType)' == ''">executable</SPPTargetType>
|
||||||
|
|
||||||
|
<OutDir>$(OutputPath)\</OutDir>
|
||||||
|
<IntDir>$(SolutionDir)cache\msbuild\</IntDir>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
|
||||||
|
|
||||||
|
<Target Name="Build" Condition="'$(SPPTargetType)' != 'meta'">
|
||||||
|
<Exec Command="$(SConsCommandLine) -j$(SPPNumProcs) --build_type=$(SPPBuildType) --unity=disable $(TargetPath)"
|
||||||
|
WorkingDirectory="$(SolutionDir)" />
|
||||||
|
</Target>
|
||||||
|
<!--<Target Name="Build" Condition="'$(SPPTargetType)' == 'meta'">
|
||||||
|
<Message Importance="low" Text="Skipping build for meta target $(ProjectName)" />
|
||||||
|
</Target>-->
|
||||||
|
<Target Name="Clean" Condition="'$(SPPTargetType)' != 'meta'">
|
||||||
|
<Exec Command="$(SConsCommandLine) -c -j$(SPPNumProcs) --build_type=$(SPPBuildType) --unity=disable $(TargetPath)"
|
||||||
|
WorkingDirectory="$(SolutionDir)" />
|
||||||
|
</Target>
|
||||||
|
<!--<Target Name="Clean" Condition="'$(SPPTargetType)' == 'meta'">
|
||||||
|
<Message Importance="low" Text="Skipping clean for meta target $(ProjectName)" />
|
||||||
|
</Target>-->
|
||||||
|
<Target Name="Rebuild" Condition="'$(SPPTargetType)' != 'meta'" DependsOnTargets="Clean;Build" />
|
||||||
|
<!--<Target Name="Rebuild" Condition="'$(SPPTargetType)' == 'meta'">
|
||||||
|
<Message Importance="low" Text="Skipping rebuild for meta target $(ProjectName)" />
|
||||||
|
</Target>-->
|
||||||
|
|
||||||
|
<!-- This target is needed just to suppress "warning NU1503: Skipping restore for project '...'. The project file may be invalid or missing targets
|
||||||
|
required for restore." -->
|
||||||
|
<Target Name="_IsProjectRestoreSupported" Returns="@(_ValidProjectsForRestore)">
|
||||||
|
<ItemGroup>
|
||||||
|
<_ValidProjectsForRestore Include="$(MSBuildProjectFullPath)" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<Import Condition="'$(_ImportMicrosoftCppDesignTime)' != 'false'" Project="$(VCTargetsPathActual)\Microsoft.Cpp.DesignTime.targets" />
|
||||||
|
</Project>
|
38
util/vs_project_template/solution.sln.jinja
Normal file
38
util/vs_project_template/solution.sln.jinja
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.10.35122.118
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
{%- for executable in project.executables %}
|
||||||
|
Project("{{ generate_uuid(project.name, True) }}") = "{{ executable.name }}", "vs_project_files\{{ executable.name }}.vcxproj", ""{{ generate_uuid('target_' + executable.name, True) }}""
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{{ generate_uuid('solution_items', True) }}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
SConstruct = SConstruct
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
{%- endfor %}
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
{%- for build_type in project.build_types %}
|
||||||
|
{%- set build_type_name = build_type | capitalize %}
|
||||||
|
{{ build_type_name }}|x64 = {{ build_type_name }}|x64
|
||||||
|
{%- endfor %}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{%- for executable in project.executables %}
|
||||||
|
{%- for build_type in project.build_types %}
|
||||||
|
{%- set build_type_name = build_type | capitalize %}
|
||||||
|
{{ generate_uuid('target_' + executable.name, True) }}.{{ build_type_name }}|x64.ActiveCfg = {{ build_type_name }}|x64
|
||||||
|
{{ generate_uuid('target_' + executable.name, True) }}.{{ build_type_name }}|x64.Build.0 = {{ build_type_name }}|x64
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endfor %}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {{ generate_uuid("solution", True) }}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
|
|
15
util/vs_project_template/template.json
Normal file
15
util/vs_project_template/template.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"files": {
|
||||||
|
"solution.sln.jinja": {
|
||||||
|
"rename_to": "{{ project.name }}.sln"
|
||||||
|
},
|
||||||
|
"vs_project_files/target.vcxproj.jinja": {
|
||||||
|
"one_per": "target",
|
||||||
|
"rename_to": "vs_project_files/{{ target.name }}.vcxproj"
|
||||||
|
},
|
||||||
|
"vs_project_files/target.vcxproj.filters.jinja": {
|
||||||
|
"one_per": "target",
|
||||||
|
"rename_to": "vs_project_files/{{ target.name }}.vcxproj.filters"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
{%- set source_files = get_sources(target) -%}
|
||||||
|
{%- set private_headers = get_headers('private\\' + target.module.folder) -%}
|
||||||
|
{%- set public_headers = get_headers('public\\' + target.module.folder) -%}
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Source Files">
|
||||||
|
<UniqueIdentifier>{{ generate_uuid('filter_sources_' + target.name, True) }}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
|
{%- for folder in source_files | folder_list(2) | sort %}
|
||||||
|
<Filter Include="Source Files\{{ folder }}">
|
||||||
|
<UniqueIdentifier>{{ generate_uuid('filter_sources_' + target.name + '_' + folder, True) }}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- if public_headers | length > 0 %}
|
||||||
|
<Filter Include="Public Header Files">
|
||||||
|
<UniqueIdentifier>{{ generate_uuid('filter_public_headers_' + target.name, True) }}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
|
{%- for folder in public_headers | folder_list(2) | sort %}
|
||||||
|
<Filter Include="Public Header Files\{{ folder }}">
|
||||||
|
<UniqueIdentifier>{{ generate_uuid('filter_public_headers_' + target.name + '_' + folder, True) }}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- if private_headers | length > 0 %}
|
||||||
|
<Filter Include="Private Header Files">
|
||||||
|
<UniqueIdentifier>{{ generate_uuid('filter_private_headers_' + target.name, True) }}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
|
{%- for folder in private_headers | folder_list(2) | sort %}
|
||||||
|
<Filter Include="Private Header Files\{{ folder }}">
|
||||||
|
<UniqueIdentifier>{{ generate_uuid('filter_private_headers_' + target.name + '_' + folder, True) }}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
{%- for source_file in source_files %}
|
||||||
|
<ClCompile Include="$(SolutionDir){{ source_file }}">
|
||||||
|
{%- set path = source_file | strip_path_prefix(2) | dirname -%}
|
||||||
|
{%- if path %}
|
||||||
|
<Filter>Source Files\{{ path }}</Filter>
|
||||||
|
{%- else %}
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
{%- endif %}
|
||||||
|
</ClCompile>
|
||||||
|
{%- endfor %}
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
{%- for header_file in public_headers %}
|
||||||
|
<ClInclude Include="$(SolutionDir){{ header_file }}">
|
||||||
|
{%- set path = header_file | strip_path_prefix(2) | dirname -%}
|
||||||
|
{%- if path %}
|
||||||
|
<Filter>Public Header Files\{{ path }}</Filter>
|
||||||
|
{%- else %}
|
||||||
|
<Filter>Public Header Files</Filter>
|
||||||
|
{%- endif %}
|
||||||
|
</ClInclude>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- for header_file in private_headers %}
|
||||||
|
<ClInclude Include="$(SolutionDir){{ header_file }}">
|
||||||
|
{%- set path = header_file | strip_path_prefix(2) | dirname -%}
|
||||||
|
{%- if path %}
|
||||||
|
<Filter>Private Header Files\{{ path }}</Filter>
|
||||||
|
{%- else %}
|
||||||
|
<Filter>Private Header Files</Filter>
|
||||||
|
{%- endif %}
|
||||||
|
</ClInclude>
|
||||||
|
{%- endfor %}
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="$(SolutionDir)private\{{ target.module.folder }}\SModule" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
@ -0,0 +1,67 @@
|
|||||||
|
{%- set ms_cxx_standard = {
|
||||||
|
'c++14': 'stdcpp14',
|
||||||
|
'c++17': 'stdcpp17',
|
||||||
|
'c++20': 'stdcpp20',
|
||||||
|
'c++23': 'stdcpplatest',
|
||||||
|
'c++26': 'stdcpplatest'}[project.cxx_standard] | default('stdcpp14')
|
||||||
|
-%}
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
{%- for build_type in project.build_types %}
|
||||||
|
{% set build_type_name = build_type | capitalize -%}
|
||||||
|
<ProjectConfiguration Include="{{ build_type_name }}|x64">
|
||||||
|
<Configuration>{{ build_type_name }}</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
{%- endfor %}
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<ProjectGuid>{{ generate_uuid('target_' + target.name, True) }}</ProjectGuid>
|
||||||
|
<ProjectName>{{ target.name }}</ProjectName>
|
||||||
|
<SConsCommandLine>{{ scons_exe }}</SConsCommandLine>
|
||||||
|
</PropertyGroup>
|
||||||
|
{%- for build_type in project.build_types %}
|
||||||
|
{% set build_type_name = build_type | capitalize -%}
|
||||||
|
<PropertyGroup Condition="'$(Configuration)'=='{{ build_type_name }}'">
|
||||||
|
<TargetPath>$(SolutionDir){{ target.filename(build_type) }}</TargetPath>
|
||||||
|
<SPPBuildType>{{ build_type }}</SPPBuildType>
|
||||||
|
<SPPTargetType>{{ target.type }}</SPPTargetType>
|
||||||
|
</PropertyGroup>
|
||||||
|
{%- endfor %}
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Label="Configuration">
|
||||||
|
<ConfigurationType>Makefile</ConfigurationType>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ItemGroup>
|
||||||
|
{%- for source_file in get_sources(target) %}
|
||||||
|
<ClCompile Include="$(SolutionDir){{ source_file }}" />
|
||||||
|
{%- endfor %}
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
{%- for header_file in get_headers('private\\' + target.module.folder) %}
|
||||||
|
<ClInclude Include="$(SolutionDir){{ header_file }}" />
|
||||||
|
{%- endfor %}
|
||||||
|
{%- for header_file in get_headers('public\\' + target.module.folder) %}
|
||||||
|
<ClInclude Include="$(SolutionDir){{ header_file }}" />
|
||||||
|
{%- endfor %}
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="$(SolutionDir)private\{{ target.module.folder }}\SModule" />
|
||||||
|
</ItemGroup>
|
||||||
|
{%- for build_type in project.build_types %}
|
||||||
|
{% set build_type_name = build_type | capitalize -%}
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)'=='{{ build_type_name }}'">
|
||||||
|
<ClCompile>
|
||||||
|
<PreprocessorDefinitions>{{ get_target_property(build_type, target.name, 'CPPDEFINES') | join(';') }};%(PreprocessorDefinitions);</PreprocessorDefinitions>
|
||||||
|
<GenerateDebugInformation>{{ build_type != 'release' and 'true' or 'false' }}</GenerateDebugInformation>
|
||||||
|
<AdditionalIncludeDirectories>{{ get_target_property(build_type, target.name, 'CPPPATH') | join(';') }};%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
<MsExtensions>false</MsExtensions>
|
||||||
|
<CppLanguageStd>{{ ms_cxx_standard }}</CppLanguageStd>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
{%- endfor %}
|
||||||
|
<Import Project="$(SolutionDir)external\scons-plus-plus\contrib\vs\spp.targets" />
|
||||||
|
</Project>
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{% for executable in project.executables %}
|
{%- for executable in project.executables -%}
|
||||||
{% for build_type in project.build_types %}
|
{%- for build_type in project.build_types -%}
|
||||||
{% set build_type_name = build_type | capitalize -%}
|
{%- set build_type_name = build_type | capitalize %}
|
||||||
{
|
{
|
||||||
"name": "{{ executable.name }} ({{ build_type | capitalize }})",
|
"name": "{{ executable.name }} ({{ build_type | capitalize }})",
|
||||||
"type": "cppvsdbg",
|
"type": "cppvsdbg",
|
||||||
@ -12,9 +12,10 @@
|
|||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"environment": [],
|
"environment": [],
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal",
|
||||||
}
|
"preLaunchTask": "{{ executable.name }} {{ build_type_name }}"
|
||||||
{% endfor %}
|
},
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
|
{%- endfor %}
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user