@@ -1076,7 +1076,31 @@ struct cli_build { | |||
{"out"}, | |||
dds::fs::current_path() / "_build"}; | |||
args::ValueFlagList<std::string> add_repos{ | |||
cmd, | |||
"<repo-url>", | |||
"Add the given repositories to the catalog before executing (Implies '--update-repos')", | |||
{"add-repo"}}; | |||
args::Flag update_repos{cmd, | |||
"update-repos", | |||
"Update repositories before building", | |||
{"update-repos", 'U'}}; | |||
int run() { | |||
if (!add_repos.Get().empty()) { | |||
auto cat = cat_path.open(); | |||
for (auto& str : add_repos.Get()) { | |||
auto repo = dds::remote_repository::connect(str); | |||
repo.store(cat.database()); | |||
} | |||
} | |||
if (update_repos.Get() || !add_repos.Get().empty()) { | |||
auto cat = cat_path.open(); | |||
dds::update_all_remotes(cat.database()); | |||
} | |||
dds::sdist_build_params main_params = { | |||
.subdir = "", | |||
.build_tests = !no_tests.Get(), |
@@ -2,14 +2,28 @@ from contextlib import ExitStack | |||
from typing import Optional | |||
from pathlib import Path | |||
import shutil | |||
from subprocess import check_call | |||
import pytest | |||
from tests import scoped_dds, DDSFixtureParams | |||
@pytest.fixture(scope='session') | |||
def dds_exe() -> Path: | |||
return Path(__file__).absolute().parent.parent / '_build/dds' | |||
@pytest.yield_fixture(scope='session') | |||
def dds_pizza_catalog(dds_exe: Path, tmp_path_factory) -> Path: | |||
tmpdir: Path = tmp_path_factory.mktemp(basename='dds-catalog') | |||
cat_path = tmpdir / 'catalog.db' | |||
check_call([str(dds_exe), 'repo', 'add', 'https://dds.pizza/repo', '--update', f'--catalog={cat_path}']) | |||
yield cat_path | |||
@pytest.yield_fixture | |||
def dds(request, tmp_path: Path, worker_id: str, scope: ExitStack): | |||
def dds(request, dds_exe: Path, tmp_path: Path, worker_id: str, scope: ExitStack): | |||
test_source_dir = Path(request.fspath).absolute().parent | |||
test_root = test_source_dir | |||
@@ -29,8 +43,7 @@ def dds(request, tmp_path: Path, worker_id: str, scope: ExitStack): | |||
project_dir = test_root / params.subdir | |||
# Create the instance. Auto-clean when we're done | |||
yield scope.enter_context( | |||
scoped_dds(test_root, project_dir, request.function.__name__)) | |||
yield scope.enter_context(scoped_dds(dds_exe, test_root, project_dir, request.function.__name__)) | |||
@pytest.fixture | |||
@@ -41,15 +54,11 @@ def scope(): | |||
def pytest_addoption(parser): | |||
parser.addoption( | |||
'--test-deps', | |||
action='store_true', | |||
default=False, | |||
help='Run the exhaustive and intensive dds-deps tests') | |||
'--test-deps', action='store_true', default=False, help='Run the exhaustive and intensive dds-deps tests') | |||
def pytest_configure(config): | |||
config.addinivalue_line( | |||
'markers', 'deps_test: Deps tests are slow. Enable with --test-deps') | |||
config.addinivalue_line('markers', 'deps_test: Deps tests are slow. Enable with --test-deps') | |||
def pytest_collection_modifyitems(config, items): | |||
@@ -60,6 +69,4 @@ def pytest_collection_modifyitems(config, items): | |||
continue | |||
item.add_marker( | |||
pytest.mark.skip( | |||
reason= | |||
'Exhaustive deps tests are slow and perform many Git clones. Use --test-deps to run them.' | |||
)) | |||
reason='Exhaustive deps tests are slow and perform many Git clones. Use --test-deps to run them.')) |
@@ -2,7 +2,7 @@ import os | |||
import itertools | |||
from contextlib import contextmanager, ExitStack | |||
from pathlib import Path | |||
from typing import Iterable, Union, Any, Dict, NamedTuple, ContextManager | |||
from typing import Iterable, Union, Any, Dict, NamedTuple, ContextManager, Optional | |||
import subprocess | |||
import shutil | |||
@@ -80,24 +80,31 @@ class DDS: | |||
args, | |||
]) | |||
def repo_add(self, url: str) -> None: | |||
return self.run(['repo', 'add', url, '--update', self.catalog_path_arg]) | |||
def build(self, | |||
*, | |||
toolchain: str = None, | |||
apps: bool = True, | |||
warnings: bool = True, | |||
catalog_path: Optional[Path] = None, | |||
tests: bool = True, | |||
more_args: proc.CommandLine = [], | |||
check: bool = True) -> subprocess.CompletedProcess: | |||
catalog_path = catalog_path or self.catalog_path.relative_to(self.source_root) | |||
return self.run( | |||
[ | |||
'build', | |||
f'--out={self.build_dir}', | |||
f'--toolchain={toolchain or self.default_builtin_toolchain}', | |||
f'--catalog={self.catalog_path.relative_to(self.source_root)}', | |||
f'--catalog={catalog_path}', | |||
f'--repo-dir={self.repo_dir.relative_to(self.source_root)}', | |||
['--no-tests'] if not tests else [], | |||
['--no-apps'] if not apps else [], | |||
['--no-warnings'] if not warnings else [], | |||
self.project_dir_arg, | |||
more_args, | |||
], | |||
check=check, | |||
) | |||
@@ -162,8 +169,7 @@ class DDS: | |||
@contextmanager | |||
def scoped_dds(test_dir: Path, project_dir: Path, name: str): | |||
dds_exe = Path(__file__).absolute().parent.parent / '_build/dds' | |||
def scoped_dds(dds_exe: Path, test_dir: Path, project_dir: Path, name: str): | |||
if os.name == 'nt': | |||
dds_exe = dds_exe.with_suffix('.exe') | |||
with ExitStack() as scope: |
@@ -23,23 +23,19 @@ class DepsCase(NamedTuple): | |||
'depends': [self.dep], | |||
}).encode())) | |||
dds.scope.enter_context( | |||
fileutil.set_contents( | |||
dds.source_root / 'library.json', | |||
json.dumps({ | |||
'name': 'test', | |||
'uses': [self.usage], | |||
}).encode())) | |||
dds.scope.enter_context( | |||
fileutil.set_contents(dds.source_root / 'src/test.test.cpp', | |||
self.source.encode())) | |||
fileutil.set_contents(dds.source_root / 'library.json', | |||
json.dumps({ | |||
'name': 'test', | |||
'uses': [self.usage], | |||
}).encode())) | |||
dds.scope.enter_context(fileutil.set_contents(dds.source_root / 'src/test.test.cpp', self.source.encode())) | |||
CASES: List[DepsCase] = [] | |||
def get_default_pkg_versions(pkg: str) -> Sequence[str]: | |||
catalog_json = Path( | |||
__file__).resolve().parent.parent.parent / 'catalog.json' | |||
catalog_json = Path(__file__).resolve().parent.parent.parent / 'catalog.json' | |||
catalog_dict = json.loads(catalog_json.read_text()) | |||
return list(catalog_dict['packages'][pkg].keys()) | |||
@@ -158,8 +154,7 @@ add_cases( | |||
## ## ## ## | |||
## ## ## ## | |||
""" | |||
add_cases( | |||
'fmt', 'fmt/fmt', ['auto'], r''' | |||
add_cases('fmt', 'fmt/fmt', ['auto'], r''' | |||
#include <fmt/core.h> | |||
int main() { | |||
@@ -546,8 +541,7 @@ add_cases( | |||
## ## ## ## ## ## ## ## ## ## | |||
###### ## ######## ######## ####### ###### | |||
""" | |||
add_cases( | |||
'spdlog', 'spdlog/spdlog', ['auto'], r''' | |||
add_cases('spdlog', 'spdlog/spdlog', ['auto'], r''' | |||
#include <spdlog/spdlog.h> | |||
int main() { | |||
@@ -582,6 +576,6 @@ add_cases( | |||
@pytest.mark.deps_test | |||
@pytest.mark.parametrize('case', CASES, ids=[c.dep for c in CASES]) | |||
def test_dep(case: DepsCase, dds: DDS) -> None: | |||
def test_dep(case: DepsCase, dds_pizza_catalog: Path, dds: DDS) -> None: | |||
case.setup_root(dds) | |||
dds.build() | |||
dds.build(catalog_path=dds_pizza_catalog) |
@@ -9,7 +9,7 @@ import subprocess | |||
import urllib.request | |||
import shutil | |||
from self_build import self_build | |||
from self_build import self_build, dds_build | |||
from dds_ci import paths, proc | |||
@@ -35,8 +35,7 @@ def _do_bootstrap_download() -> None: | |||
'freebsd12': 'dds-freebsd-x64', | |||
}.get(sys.platform) | |||
if filename is None: | |||
raise RuntimeError(f'We do not have a prebuilt DDS binary for ' | |||
f'the "{sys.platform}" platform') | |||
raise RuntimeError(f'We do not have a prebuilt DDS binary for the "{sys.platform}" platform') | |||
url = f'https://github.com/vector-of-bool/dds/releases/download/0.1.0-alpha.4/{filename}' | |||
print(f'Downloading prebuilt DDS executable: {url}') | |||
@@ -72,9 +71,7 @@ def main(argv: Sequence[str]) -> int: | |||
required=True, | |||
) | |||
parser.add_argument( | |||
'--build-only', | |||
action='store_true', | |||
help='Only build the `dds` executable. Skip second-phase and tests.') | |||
'--build-only', action='store_true', help='Only build the `dds` executable. Skip second-phase and tests.') | |||
parser.add_argument( | |||
'--no-clean', | |||
action='store_false', | |||
@@ -106,25 +103,30 @@ def main(argv: Sequence[str]) -> int: | |||
paths.PREBUILT_DDS, | |||
toolchain=opts.toolchain, | |||
cat_path=old_cat_path, | |||
cat_json_path=Path('catalog.old.json'), | |||
cat_json_path=Path('catalog.json'), | |||
dds_flags=[('--repo-dir', ci_repo_dir)]) | |||
print('Main build PASSED!') | |||
print(f'A `dds` executable has been generated: {paths.CUR_BUILT_DDS}') | |||
if args.build_only: | |||
print( | |||
f'`--build-only` was given, so second phase and tests will not execute' | |||
) | |||
print('`--build-only` was given, so second phase and tests will not execute') | |||
return 0 | |||
print('Bootstrapping myself:') | |||
new_cat_path = paths.BUILD_DIR / 'catalog.db' | |||
new_repo_dir = paths.BUILD_DIR / 'ci-repo' | |||
self_build( | |||
new_repo_dir = paths.BUILD_DIR / 'ci-repo-2' | |||
if new_cat_path.is_file(): | |||
new_cat_path.unlink() | |||
if new_repo_dir.is_dir(): | |||
shutil.rmtree(new_repo_dir) | |||
dds_build( | |||
paths.CUR_BUILT_DDS, | |||
toolchain=opts.toolchain, | |||
cat_path=new_cat_path, | |||
dds_flags=[f'--repo-dir={new_repo_dir}']) | |||
more_flags=[ | |||
f'--repo-dir={new_repo_dir}', | |||
f'--catalog={new_cat_path}', | |||
'--add-repo=https://dds.pizza/repo', | |||
]) | |||
print('Bootstrap test PASSED!') | |||
return pytest.main([ |
@@ -117,13 +117,6 @@ class Git(NamedTuple): | |||
d['auto-lib'] = self.auto_lib | |||
return d | |||
def to_dict_2(self) -> str: | |||
url = f'git+{self.url}' | |||
if self.auto_lib: | |||
url += f'?lm={self.auto_lib}' | |||
url += f'#{self.ref}' | |||
return url | |||
RemoteInfo = Union[Git] | |||
@@ -143,15 +136,6 @@ class Version(NamedTuple): | |||
ret['git'] = self.remote.to_dict() | |||
return ret | |||
def to_dict_2(self) -> dict: | |||
ret: dict = { | |||
'description': self.description, | |||
'depends': list(self.depends), | |||
'transform': [f.to_dict() for f in self.remote.transforms], | |||
} | |||
ret['url'] = self.remote.to_dict_2() | |||
return ret | |||
class VersionSet(NamedTuple): | |||
version: str | |||
@@ -175,8 +159,7 @@ def github_http_get(url: str): | |||
raise RuntimeError(f'Request is outside of api.github.com [{url}]') | |||
resp = request.urlopen(req) | |||
if resp.status != 200: | |||
raise RuntimeError( | |||
f'Request to [{url}] failed [{resp.status} {resp.reason}]') | |||
raise RuntimeError(f'Request to [{url}] failed [{resp.status} {resp.reason}]') | |||
return json5.loads(resp.read()) | |||
@@ -188,8 +171,7 @@ def _get_github_tree_file_content(url: str) -> bytes: | |||
return content | |||
def _version_for_github_tag(pkg_name: str, desc: str, clone_url: str, | |||
tag) -> Version: | |||
def _version_for_github_tag(pkg_name: str, desc: str, clone_url: str, tag) -> Version: | |||
print(f'Loading tag {tag["name"]}') | |||
commit = github_http_get(tag['commit']['url']) | |||
tree = github_http_get(commit['commit']['tree']['url']) | |||
@@ -201,12 +183,9 @@ def _version_for_github_tag(pkg_name: str, desc: str, clone_url: str, | |||
package_json_fname = cand | |||
break | |||
else: | |||
raise RuntimeError( | |||
f'No package JSON5 file in tag {tag["name"]} for {pkg_name} (One of {tree_content.keys()})' | |||
) | |||
raise RuntimeError(f'No package JSON5 file in tag {tag["name"]} for {pkg_name} (One of {tree_content.keys()})') | |||
package_json = json5.loads( | |||
_get_github_tree_file_content(tree_content[package_json_fname]['url'])) | |||
package_json = json5.loads(_get_github_tree_file_content(tree_content[package_json_fname]['url'])) | |||
version = package_json['version'] | |||
if pkg_name != package_json['name']: | |||
raise RuntimeError(f'package name in repo "{package_json["name"]}" ' | |||
@@ -221,14 +200,10 @@ def _version_for_github_tag(pkg_name: str, desc: str, clone_url: str, | |||
elif depends is None: | |||
pairs = [] | |||
else: | |||
raise RuntimeError( | |||
f'Unknown "depends" object from json file: {depends!r}') | |||
raise RuntimeError(f'Unknown "depends" object from json file: {depends!r}') | |||
remote = Git(url=clone_url, ref=tag['name']) | |||
return Version(version, | |||
description=desc, | |||
depends=list(pairs), | |||
remote=remote) | |||
return Version(version, description=desc, depends=list(pairs), remote=remote) | |||
def github_package(name: str, repo: str, want_tags: Iterable[str]) -> Package: | |||
@@ -239,15 +214,12 @@ def github_package(name: str, repo: str, want_tags: Iterable[str]) -> Package: | |||
missing_tags = set(want_tags) - set(t['name'] for t in avail_tags) | |||
if missing_tags: | |||
raise RuntimeError( | |||
'One or more wanted tags do not exist in ' | |||
f'the repository "{repo}" (Missing: {missing_tags})') | |||
raise RuntimeError('One or more wanted tags do not exist in ' | |||
f'the repository "{repo}" (Missing: {missing_tags})') | |||
tag_items = (t for t in avail_tags if t['name'] in want_tags) | |||
versions = HTTP_POOL.map( | |||
lambda tag: _version_for_github_tag(name, desc, repo_data['clone_url'], | |||
tag), tag_items) | |||
versions = HTTP_POOL.map(lambda tag: _version_for_github_tag(name, desc, repo_data['clone_url'], tag), tag_items) | |||
return Package(name, list(versions)) | |||
@@ -260,11 +232,11 @@ def simple_packages(name: str, | |||
*, | |||
tag_fmt: str = '{}') -> Package: | |||
return Package(name, [ | |||
Version(ver.version, | |||
description=description, | |||
remote=Git( | |||
git_url, tag_fmt.format(ver.version), auto_lib=auto_lib), | |||
depends=ver.depends) for ver in versions | |||
Version( | |||
ver.version, | |||
description=description, | |||
remote=Git(git_url, tag_fmt.format(ver.version), auto_lib=auto_lib), | |||
depends=ver.depends) for ver in versions | |||
]) | |||
@@ -277,12 +249,11 @@ def many_versions(name: str, | |||
transforms: Sequence[FSTransform] = (), | |||
description='(No description was provided)') -> Package: | |||
return Package(name, [ | |||
Version(ver, | |||
description='\n'.join(textwrap.wrap(description)), | |||
remote=Git(url=git_url, | |||
ref=tag_fmt.format(ver), | |||
auto_lib=auto_lib, | |||
transforms=transforms)) for ver in versions | |||
Version( | |||
ver, | |||
description='\n'.join(textwrap.wrap(description)), | |||
remote=Git(url=git_url, ref=tag_fmt.format(ver), auto_lib=auto_lib, transforms=transforms)) | |||
for ver in versions | |||
]) | |||
@@ -290,7 +261,7 @@ def many_versions(name: str, | |||
PACKAGES = [ | |||
github_package('neo-buffer', 'vector-of-bool/neo-buffer', | |||
['0.2.1', '0.3.0', '0.4.0', '0.4.1', '0.4.2']), | |||
github_package('neo-compress', 'vector-of-bool/neo-compress', ['0.1.0', '0.1.1']), | |||
github_package('neo-compress', 'vector-of-bool/neo-compress', ['0.1.0', '0.1.1', '0.2.0']), | |||
github_package('neo-url', 'vector-of-bool/neo-url', | |||
['0.1.0', '0.1.1', '0.1.2', '0.2.0', '0.2.1', '0.2.2']), | |||
github_package('neo-sqlite3', 'vector-of-bool/neo-sqlite3', | |||
@@ -954,22 +925,9 @@ if __name__ == "__main__": | |||
args = parser.parse_args() | |||
data = { | |||
'version': 2, | |||
'packages': { | |||
pkg.name: {ver.version: ver.to_dict_2() | |||
for ver in pkg.versions} | |||
for pkg in PACKAGES | |||
} | |||
} | |||
old_data = { | |||
'version': 1, | |||
'packages': { | |||
pkg.name: {ver.version: ver.to_dict() | |||
for ver in pkg.versions} | |||
for pkg in PACKAGES | |||
} | |||
'packages': {pkg.name: {ver.version: ver.to_dict() | |||
for ver in pkg.versions} | |||
for pkg in PACKAGES} | |||
} | |||
json_str = json.dumps(data, indent=2, sort_keys=True) | |||
Path('catalog.json').write_text(json_str) | |||
Path('catalog.old.json').write_text( | |||
json.dumps(old_data, indent=2, sort_keys=True)) | |||
Path('catalog.json').write_text(json.dumps(data, indent=2, sort_keys=True)) |
@@ -11,6 +11,15 @@ from dds_ci import cli, proc | |||
ROOT = Path(__file__).parent.parent.absolute() | |||
def dds_build(exe: Path, *, toolchain: str, more_flags: proc.CommandLine = ()): | |||
new_exe = ROOT / '_dds.bootstrap-test.exe' | |||
shutil.copy2(exe, new_exe) | |||
try: | |||
proc.check_run(new_exe, 'build', (f'--toolchain={toolchain}'), more_flags) | |||
finally: | |||
new_exe.unlink() | |||
def self_build(exe: Path, | |||
*, | |||
toolchain: str, | |||
@@ -20,27 +29,23 @@ def self_build(exe: Path, | |||
dds_flags: proc.CommandLine = ()): | |||
# Copy the exe to another location, as windows refuses to let a binary be | |||
# replaced while it is executing | |||
new_exe = ROOT / '_dds.bootstrap-test.exe' | |||
shutil.copy2(exe, new_exe) | |||
try: | |||
proc.check_run( | |||
new_exe, | |||
'catalog', | |||
'import', | |||
f'--catalog={cat_path}', | |||
f'--json={cat_json_path}', | |||
) | |||
proc.check_run( | |||
new_exe, | |||
'build', | |||
f'--catalog={cat_path}', | |||
f'--repo-dir={ROOT}/_build/ci-repo', | |||
dds_flags, | |||
('--toolchain', toolchain), | |||
proc.check_run( | |||
exe, | |||
'catalog', | |||
'import', | |||
f'--catalog={cat_path}', | |||
f'--json={cat_json_path}', | |||
) | |||
dds_build( | |||
exe, | |||
toolchain=toolchain, | |||
more_flags=[ | |||
('-I', lmi_path) if lmi_path else (), | |||
) | |||
finally: | |||
new_exe.unlink() | |||
f'--repo-dir={ROOT}/_build/ci-repo', | |||
f'--catalog={cat_path}', | |||
*dds_flags, | |||
], | |||
) | |||
def main(argv: List[str]) -> int: |