Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Поддержка различных вариантов сборки #45

Merged
merged 6 commits into from
Jan 16, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include kks/data/targets.yaml
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,34 @@ kks hide --all
kks unhide sm03 kr01
```

## Файлы конфигурации

Используются, если нужно изменить флаги компилятора по умолчанию / добавить дополнительные варианты сборки (например, для дебага).

Если в корневой директории воркспейса существует файл `targets.yaml`, то все таргеты из него доступны в любой поддиректории (являются глобальными).
Если `targets.yaml` существует в рабочей директории, то описания таргетов из него являются более приоритетными по сравнению с глобальными.

```shell script
# Создать файл в рабочей директории
kks init --config

# Создать глобальный конфиг для существующего воркспейса
kks init --config=global

# Собрать и запустить решение с таргетом "debug"
kks run -tg debug
```

При запуске `kks run` или `kks test` можно получить предупреждение следующего вида:

```
/path/to/targets.yaml is outdated. You can run "kks init --config=update" if you want to update the default target manually
```

Это значит, что параметры сборки по умолчанию (без использования файлов конфигурации) были обновлены.
В таком случае стоит запустить `kks init --config=update` в директории с указанным файлом и вручную добавить необходимые изменения.
Если этого не cделать, могут появиться проблемы при компиляции решений для (некоторых) новых задач.

## Todo
- run
- [ ] fix valgrind issues
Expand Down
63 changes: 39 additions & 24 deletions kks/binary.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import os
import subprocess
import sys
from itertools import chain

import click

GCC_ARGS = [
'gcc',
'-std=gnu11',
'-g',
'-Werror',
'-Wall',
'-Wextra',
'-ftrapv', # catch signed overflow on addition, subtraction, multiplication operations
]
from kks.util.config import find_target


GPP_ARGS = [
'g++',
Expand All @@ -35,26 +29,31 @@
'ASAN_OPTIONS': 'color=always',
}

LINK_ARGS = [
'-lm',
]

VALGRIND_ARGS = [
'valgrind',
'--leak-check=full',
]


def compile_solution(directory, options):
c_files = list(directory.glob('*.c'))
def compile_solution(directory, target_name, verbose, options):
target = find_target(target_name)
if target is None:
click.secho(f'No target {target_name} found', fg='red', err=True)
return None

if verbose:
darkkeks marked this conversation as resolved.
Show resolved Hide resolved
click.secho(f'Selected target: {target}')

# gcc can compile c and asm files together, so everything should be ok
source_files = list(chain(*[directory.glob(f) for f in target.files]))

if len(c_files) == 0:
click.secho('No .c files found', fg='yellow', err=True)
if len(source_files) == 0:
click.secho('No source files found', fg='yellow', err=True)
return None

click.secho('Compiling... ', fg='green', err=True, nl=False)

binary = compile_c(directory, c_files, options)
binary = compile_c(directory, source_files, target, verbose, options)

if binary is None:
click.secho('Compilation failed!', fg='red', err=True)
Expand All @@ -66,29 +65,45 @@ def compile_solution(directory, options):
return binary


def compile_c(workdir, files, options):
return compile_gnu(workdir, files, options, list(GCC_ARGS))
def compile_c(workdir, files, target, verbose, options):
compiler_args = ['gcc'] + target.flags
if not target.asm64bit and any(f.suffix.lower() == '.s' for f in files):
compiler_args.append('-m32')
return compile_gnu(
workdir,
files,
options,
compiler_args,
linker_args=[f'-l{lib}' for lib in target.libs],
out_file=target.out,
verbose=verbose
)


def compile_cpp(workdir, files, options):
return compile_gnu(workdir, files, options, list(GPP_ARGS))
return compile_gnu(workdir, files, options, GPP_ARGS)


def compile_gnu(workdir, files, options, compiler_args):
def compile_gnu(workdir, files, options, compiler_args, linker_args=[], out_file='', verbose=False):
filenames = [path.absolute() for path in files]

command = compiler_args
if options.asan:
command += ASAN_ARGS
if out_file:
command += ['-o', (workdir / out_file).absolute()]
command += filenames
command += LINK_ARGS
command += linker_args

if verbose:
click.secho('\nExecuting "{}"'.format(' '.join(map(str, command))))

p = subprocess.run(command, cwd=workdir)

if p.returncode != 0:
return None

return workdir / 'a.out'
return workdir / (out_file or 'a.out')


def run_solution(binary, args, options, test_data, capture_output=True):
Expand Down
80 changes: 68 additions & 12 deletions kks/cmd/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,98 @@

import click

from kks.util.common import find_workspace, get_hidden_dir
from kks.util.click import OptFlagCommand, FlagOption, OptFlagOption, Choice2
from kks.util.common import find_workspace, get_hidden_dir, format_file
from kks.util.config import target_file, global_comment, targets_version


@click.command()
@click.command(cls=OptFlagCommand)
@click.option('-f', '--force', is_flag=True)
def init(force):
@click.option('-c', '--config', cls=FlagOption, is_flag=True,
help=f'Create {target_file} in current directory and exit')
@click.option('--config_opt', cls=OptFlagOption, type=Choice2(['update', 'global']),
help=f'Create a copy of config for manual updating or create config in the root dir of workspace') # TODO multiline help?
vvd170501 marked this conversation as resolved.
Show resolved Hide resolved
def init(force, config, config_opt):
"""Initialize kks workspace in current directory."""

path = Path()
file = path / '.kks-workspace'
old_workspace = False

if config or config_opt:
if config_opt == 'global':
workspace = find_workspace()
if workspace is None:
click.secho('Current directory is not in a kks workspace. To use "global" option, you need to cd into an existing workspace or run "kks init"', fg='red')
return
path = workspace
is_global = True
else:
is_global = file.exists()
create_config(path, is_global, config_opt == 'update', force)
return

if not force:
workspace = find_workspace()
if workspace is not None:
click.secho(f'Found workspace in directory {workspace}\n'
f'If you are sure you want to create workspace here, specify --force', fg='yellow')
return

path = Path()
file = path / '.kks-workspace'

if file.exists():
if file.is_file():
click.secho('Workspace already exists', fg='green')
click.secho('Workspace already exists.', fg='green')
click.secho('Adding missing files...\n', fg='green')
old_workspace = True
elif file.is_dir():
click.secho('Workspace marker is a directory (.kks-workspace)', fg='yellow')
return
else:
click.secho('Workspace marker is an unknown file type (.kks-workspace)', fg='yellow')
return
return
else:
file.write_text("This file is used to find kks workspace.\n")
file.write_text('This file is used to find kks workspace.\n')

hidden = get_hidden_dir(path)
if hidden.exists():
if hidden.is_dir():
click.secho(f'The directory for hidden contests already exists ({hidden.relative_to(path)})', fg='yellow')
click.secho(f'The directory for hidden contests already exists ({hidden.relative_to(path)})\n', fg='yellow')
else:
click.secho(f'{hidden.relative_to(path)} exists and is not a directory, "kks (un)hide" may break!', fg='red')
click.secho(f'{hidden.relative_to(path)} exists and is not a directory, "kks (un)hide" may break!\n', fg='red')
else:
hidden.mkdir()

click.secho(f'Initialized workspace in directory {path}', fg='green')
action = 'Updated' if old_workspace else 'Initialized'
click.secho(f'{action} workspace in directory {path.absolute()}', fg='green', bold=True)


def create_config(directory, is_global, update, force):
from pkg_resources import resource_stream
file = directory / target_file
if update:
file = file.with_suffix(file.suffix + '.default')
else:
if file.exists():
if not file.is_file():
click.secho(f'"{file}" exists and is not a file, you will need to replace it to create custom targets', fg='red')
return
elif not force:
click.secho(f'The file {file} already exists. Use --force to overwrite it or --config=update to create a copy', fg='yellow')
return

data = resource_stream('kks', f'data/{target_file}').read().decode()
vvd170501 marked this conversation as resolved.
Show resolved Hide resolved
data = data.replace('KKS_TARGETS_VERSION', str(targets_version), 1)
if is_global:
data = global_comment + data
file.write_text(data)

if update:
click.secho('New default targets are written to ', fg='green', nl=False)
click.secho(format_file(file), nl=False)
click.secho(', you can merge it with ', fg='green', nl=False)
click.secho(format_file(file.with_name(target_file)), nl=False)
click.secho(' manually', fg='green')
else:
click.secho('Default targets are written to ', fg='green', nl=False)
click.secho(format_file(file))
click.secho('The config file is not updated automatically.', bold=True)
10 changes: 7 additions & 3 deletions kks/cmd/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@


@click.command(short_help='Run solution')
@click.option('-T', '-tg', '--target', default='default',
help='Target name to build')
@click.option('-v', '--verbose', is_flag=True,
help='Verbose mode (show used gcc args)')
@click.option('--asan/--no-asan', is_flag=True, default=True,
help='Use asan (true by default)')
help='Use asan (true by default)') # if there are asan flags for the selected target and '--no-asan' is used, then only flags from the config will be used
@click.option('-g', '-vg', '--valgrind', is_flag=True,
help='Use valgrind (disables asan)')
@click.option('-s', '--sample', is_flag=True,
Expand All @@ -19,7 +23,7 @@
@click.option('-f', '--file', 'file', type=click.Path(exists=True),
help='File to use as an input')
@click.argument('run_args', nargs=-1, type=click.UNPROCESSED)
def run(asan, valgrind, sample, test, file, run_args):
def run(asan, valgrind, sample, test, file, target, verbose, run_args):
"""Run solution

\b
Expand All @@ -41,7 +45,7 @@ def run(asan, valgrind, sample, test, file, run_args):
valgrind=valgrind,
)

binary = compile_solution(directory, options)
binary = compile_solution(directory, target, verbose, options)
if binary is None:
return

Expand Down
19 changes: 1 addition & 18 deletions kks/cmd/submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import click

from kks.ejudge_submit import submit_solution
from kks.util.common import get_valid_session, load_links, prompt_choice, find_workspace, get_hidden_dir
from kks.util.common import get_valid_session, load_links, prompt_choice, find_problem_rootdir


@click.command(short_help='Submit a solutions')
Expand Down Expand Up @@ -45,23 +45,6 @@ def submit(file, problem):
click.secho(msg, fg=color)


def find_problem_rootdir():
cwd = Path.cwd().resolve()
rootdir = find_workspace(cwd)
if rootdir is None:
return None
hidden = get_hidden_dir(rootdir)
try:
_ = cwd.relative_to(hidden)
rootdir = hidden
except ValueError:
pass
parts = cwd.relative_to(rootdir).parts
if len(parts) < 2:
return None
return rootdir / parts[0] / parts[1]


def get_problem_id(rootdir):
return '{}-{}'.format(*rootdir.parts[-2:])

Expand Down
4 changes: 2 additions & 2 deletions kks/cmd/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def sync(code, code_opt, force, filters):
fg='red', err=True)
bad_contests.add(contest)
continue

if not task_dir.exists():
click.secho('Creating directories for task ' + click.style(problem.name, fg='blue', bold=True))
task_dir.mkdir(parents=True, exist_ok=True)
Expand All @@ -185,7 +185,7 @@ def sync(code, code_opt, force, filters):
main.touch()

gen = task_dir / 'gen.py'

if not gen.exists():
with gen.open('w') as file:
file.write('import sys\n'
Expand Down
10 changes: 7 additions & 3 deletions kks/cmd/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@


@click.command(name='test', short_help='Test solutions')
@click.option('-T', '-tg', '--target', default='default',
help='Target name to build')
@click.option('-v', '--verbose', is_flag=True,
help='Verbose mode (show used gcc args)')
@click.option('-t', '--test', 'tests', type=int, multiple=True,
help='Test numbers to run (multiple are allowed)')
@click.option('-r', '--range', 'test_range', type=int, nargs=2,
Expand All @@ -27,13 +31,13 @@
help='Use asan (true by default)')
@click.option('-g', '-vg', '--valgrind', is_flag=True,
help='Use valgrind (disables asan)')
@click.option('-v', '-vt', '--virtual', is_flag=True,
vvd170501 marked this conversation as resolved.
Show resolved Hide resolved
@click.option('-V', '-vt', '--virtual', is_flag=True,
help='Use virtual tests (generate tests in memory)')
@click.option('--generator', type=click.Path(exists=True),
help='generator for virtual tests (see "kks gen")')
@click.option('--solution', type=click.Path(exists=True),
help='solution for virtual tests')
def test_(tests, test_range, files, sample,
def test_(target, verbose, tests, test_range, files, sample,
continue_on_error, ignore_exit_code, asan, valgrind,
virtual, generator, solution):
"""
Expand All @@ -58,7 +62,7 @@ def test_(tests, test_range, files, sample,
is_sample=sample,
)

binary = compile_solution(directory, options)
binary = compile_solution(directory, target, verbose, options)
if binary is None:
return

Expand Down
Loading