| echo Loading VS environment | echo Loading VS environment | ||||
| call "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\Common7\\Tools\\vsdevcmd" -arch=x64 || exit 1 | call "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\Common7\\Tools\\vsdevcmd" -arch=x64 || exit 1 | ||||
| echo Executing Build and Tests | echo Executing Build and Tests | ||||
| python -u tools/ci.py --cxx cl.exe -T tools\\msvc.dds || exit 1 | |||||
| python -u tools/ci.py -B download --cxx cl.exe -T tools\\msvc.dds || exit 1 | |||||
| displayName: Full CI | displayName: Full CI | ||||
| - publish: _build/dds.exe | - publish: _build/dds.exe | ||||
| artifact: DDS Executable - Windows VS2019 | artifact: DDS Executable - Windows VS2019 | ||||
| steps: | steps: | ||||
| - script: sudo apt update -y && sudo apt install -y python3-minimal g++-8 | - script: sudo apt update -y && sudo apt install -y python3-minimal g++-8 | ||||
| displayName: Prepare System | displayName: Prepare System | ||||
| - script: python3 -u tools/ci.py --cxx g++-8 -T tools/gcc-8.dds | |||||
| - script: python3 -u tools/ci.py -B download --cxx g++-8 -T tools/gcc-8.dds | |||||
| displayName: Full CI | displayName: Full CI | ||||
| - publish: _build/dds | - publish: _build/dds | ||||
| artifact: DDS Executable - Linux | artifact: DDS Executable - Linux | ||||
| steps: | steps: | ||||
| - script: brew install gcc@8 | - script: brew install gcc@8 | ||||
| displayName: Prepare System | displayName: Prepare System | ||||
| - script: python3 -u tools/ci.py --cxx g++-8 -T tools/gcc-8.dds | |||||
| - script: python3 -u tools/ci.py -B download --cxx g++-8 -T tools/gcc-8.dds | |||||
| displayName: Build and Run Unit Tests | displayName: Build and Run Unit Tests | ||||
| - publish: _build/dds | - publish: _build/dds | ||||
| artifact: DDS Executable - macOS | artifact: DDS Executable - macOS |
| lm::read_accumulate("Uses", uses_strings), | lm::read_accumulate("Uses", uses_strings), | ||||
| lm::read_accumulate("Links", links_strings), | lm::read_accumulate("Links", links_strings), | ||||
| lm::read_required("Name", ret.name), | lm::read_required("Name", ret.name), | ||||
| lm::ignore_x_keys(), | |||||
| lm::reject_unknown()); | lm::reject_unknown()); | ||||
| extend(ret.uses, ranges::views::transform(uses_strings, lm::split_usage_string)); | extend(ret.uses, ranges::views::transform(uses_strings, lm::split_usage_string)); |
| #include <dds/util/fs.hpp> | #include <dds/util/fs.hpp> | ||||
| #include <optional> | #include <optional> | ||||
| #include <vector> | |||||
| namespace dds { | namespace dds { | ||||
| void lm::write_pairs(fs::path fpath, const std::vector<pair>& pairs) { | void lm::write_pairs(fs::path fpath, const std::vector<pair>& pairs) { | ||||
| auto fstream = dds::open(fpath, std::ios::out | std::ios::binary); | auto fstream = dds::open(fpath, std::ios::out | std::ios::binary); | ||||
| for (auto& pair : pairs) { | for (auto& pair : pairs) { | ||||
| fstream << pair.key() << ": " << pair.value() << '\n'; | |||||
| fstream << pair.key << ": " << pair.value << '\n'; | |||||
| } | } | ||||
| } | } | ||||
| namespace lm { | namespace lm { | ||||
| class pair { | class pair { | ||||
| std::string _key; | |||||
| std::string _value; | |||||
| public: | public: | ||||
| std::string key; | |||||
| std::string value; | |||||
| pair(std::string_view k, std::string_view v) | pair(std::string_view k, std::string_view v) | ||||
| : _key(k) | |||||
| , _value(v) {} | |||||
| auto& key() const noexcept { return _key; } | |||||
| auto& value() const noexcept { return _value; } | |||||
| template <std::size_t I> | |||||
| std::string_view get() const { | |||||
| if constexpr (I == 0) { | |||||
| return key(); | |||||
| } else if constexpr (I == 1) { | |||||
| return value(); | |||||
| } | |||||
| } | |||||
| : key(k) | |||||
| , value(v) {} | |||||
| }; | }; | ||||
| class pair_iterator { | class pair_iterator { | ||||
| pair_iterator& operator++() & noexcept { | pair_iterator& operator++() & noexcept { | ||||
| assert(_iter != _end); | assert(_iter != _end); | ||||
| ++_iter; | ++_iter; | ||||
| while (_iter != _end && _iter->key() != _key) { | |||||
| while (_iter != _end && _iter->key != _key) { | |||||
| ++_iter; | ++_iter; | ||||
| } | } | ||||
| return *this; | return *this; | ||||
| const pair* find(const std::string_view& key) const noexcept { | const pair* find(const std::string_view& key) const noexcept { | ||||
| for (auto&& item : items()) { | for (auto&& item : items()) { | ||||
| if (item.key() == key) { | |||||
| if (item.key == key) { | |||||
| return &item; | return &item; | ||||
| } | } | ||||
| } | } | ||||
| pair_iterator iter(std::string_view key) const noexcept { | pair_iterator iter(std::string_view key) const noexcept { | ||||
| auto iter = items().begin(); | auto iter = items().begin(); | ||||
| const auto end = items().end(); | const auto end = items().end(); | ||||
| while (iter != end && iter->key() != key) { | |||||
| while (iter != end && iter->key != key) { | |||||
| ++iter; | ++iter; | ||||
| } | } | ||||
| return pair_iterator{iter, end, key}; | return pair_iterator{iter, end, key}; | ||||
| } | } | ||||
| }; | }; | ||||
| class ignore_x_keys { | |||||
| public: | |||||
| bool operator()(std::string_view, std::string_view key, std::string_view) const { | |||||
| return key.find("X-") == 0; | |||||
| } | |||||
| }; | |||||
| class reject_unknown { | class reject_unknown { | ||||
| public: | public: | ||||
| int operator()(std::string_view context, std::string_view key, std::string_view) const { | int operator()(std::string_view context, std::string_view key, std::string_view) const { | ||||
| } | } | ||||
| } // namespace lm | } // namespace lm | ||||
| namespace std { | |||||
| template <> | |||||
| struct tuple_size<lm::pair> : std::integral_constant<int, 2> {}; | |||||
| template <std::size_t N> | |||||
| struct tuple_element<N, lm::pair> { | |||||
| using type = std::string_view; | |||||
| }; | |||||
| } // namespace std |
| kvs = parse_string(lm_src); | kvs = parse_string(lm_src); | ||||
| CHECK(kvs.size() == 1); | CHECK(kvs.size() == 1); | ||||
| REQUIRE(kvs.find("foo")); | REQUIRE(kvs.find("foo")); | ||||
| CHECK(kvs.find("foo")->value() == "bar"); | |||||
| CHECK(kvs.find("foo")->value == "bar"); | |||||
| lm_src = "foo:bar: baz"; | lm_src = "foo:bar: baz"; | ||||
| kvs = parse_string(lm_src); | kvs = parse_string(lm_src); | ||||
| CHECK(kvs.size() == 1); | CHECK(kvs.size() == 1); | ||||
| REQUIRE(kvs.find("foo:bar")); | REQUIRE(kvs.find("foo:bar")); | ||||
| CHECK(kvs.find("foo:bar")->value() == "baz"); | |||||
| CHECK(kvs.find("foo:bar")->value == "baz"); | |||||
| CHECK(parse_string("#comment").size() == 0); | CHECK(parse_string("#comment").size() == 0); | ||||
| CHECK(parse_string("\n\n").size() == 0); | CHECK(parse_string("\n\n").size() == 0); | ||||
| kvs = parse_string(s); | kvs = parse_string(s); | ||||
| CHECK(kvs.size() == 1); | CHECK(kvs.size() == 1); | ||||
| REQUIRE(kvs.find("Foo")); | REQUIRE(kvs.find("Foo")); | ||||
| CHECK(kvs.find("Foo")->value() == ""); | |||||
| CHECK(kvs.find("Foo")->value == ""); | |||||
| } | } | ||||
| kvs = parse_string("foo: # Not a comment"); | kvs = parse_string("foo: # Not a comment"); | ||||
| CHECK(kvs.size() == 1); | CHECK(kvs.size() == 1); | ||||
| REQUIRE(kvs.find("foo")); | REQUIRE(kvs.find("foo")); | ||||
| CHECK(kvs.find("foo")->value() == "# Not a comment"); | |||||
| CHECK(kvs.find("foo")->value == "# Not a comment"); | |||||
| } | } | ||||
| void test_multi() { | void test_multi() { | ||||
| auto kvs = parse_string("Foo: bar\nbaz: qux"); | auto kvs = parse_string("Foo: bar\nbaz: qux"); | ||||
| CHECK(kvs.size() == 2); | CHECK(kvs.size() == 2); | ||||
| REQUIRE(kvs.find("Foo")); | REQUIRE(kvs.find("Foo")); | ||||
| CHECK(kvs.find("Foo")->value() == "bar"); | |||||
| CHECK(kvs.find("Foo")->value == "bar"); | |||||
| REQUIRE(kvs.find("baz")); | REQUIRE(kvs.find("baz")); | ||||
| CHECK(kvs.find("baz")->value() == "qux"); | |||||
| CHECK(kvs.find("baz")->value == "qux"); | |||||
| kvs = parse_string("foo: first\nfoo: second\n"); | kvs = parse_string("foo: first\nfoo: second\n"); | ||||
| CHECK(kvs.size() == 2); | CHECK(kvs.size() == 2); | ||||
| auto iter = kvs.iter("foo"); | auto iter = kvs.iter("foo"); | ||||
| REQUIRE(iter); | REQUIRE(iter); | ||||
| CHECK(iter->key() == "foo"); | |||||
| CHECK(iter->value() == "first"); | |||||
| CHECK(iter->key == "foo"); | |||||
| CHECK(iter->value == "first"); | |||||
| ++iter; | ++iter; | ||||
| REQUIRE(iter); | REQUIRE(iter); | ||||
| CHECK(iter->key() == "foo"); | |||||
| CHECK(iter->value() == "second"); | |||||
| CHECK(iter->key == "foo"); | |||||
| CHECK(iter->value == "second"); | |||||
| ++iter; | ++iter; | ||||
| CHECK(!iter); | CHECK(!iter); | ||||
| CHECK(result.primary == "Foo"); | CHECK(result.primary == "Foo"); | ||||
| CHECK(result.pairs.size() == 1); | CHECK(result.pairs.size() == 1); | ||||
| REQUIRE(result.pairs.find("bar")); | REQUIRE(result.pairs.find("bar")); | ||||
| CHECK(result.pairs.find("bar")->value() == "baz"); | |||||
| CHECK(result.pairs.find("bar")->value == "baz"); | |||||
| }; | }; | ||||
| check_1("Foo; bar=baz"); | check_1("Foo; bar=baz"); | ||||
| check_1("Foo ; bar=baz"); | check_1("Foo ; bar=baz"); | ||||
| CHECK(result.primary == "Foo bar"); | CHECK(result.primary == "Foo bar"); | ||||
| CHECK(result.pairs.size() == 2); | CHECK(result.pairs.size() == 2); | ||||
| REQUIRE(result.pairs.find("baz")); | REQUIRE(result.pairs.find("baz")); | ||||
| CHECK(result.pairs.find("baz")->value() == "meow"); | |||||
| CHECK(result.pairs.find("baz")->value == "meow"); | |||||
| REQUIRE(result.pairs.find("quux")); | REQUIRE(result.pairs.find("quux")); | ||||
| CHECK(result.pairs.find("quux")->value() == ""); | |||||
| CHECK(result.pairs.find("quux")->value == ""); | |||||
| }; | }; | ||||
| check_3("Foo bar; baz=meow quux"); | check_3("Foo bar; baz=meow quux"); |
| EXE_SUFFIX = '.exe' if os.name == 'nt' else '' | EXE_SUFFIX = '.exe' if os.name == 'nt' else '' | ||||
| def _run_quiet(args) -> None: | |||||
| cmd = [str(s) for s in args] | |||||
| res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | |||||
| def _run_quiet(cmd, **kwargs) -> None: | |||||
| cmd = [str(s) for s in cmd] | |||||
| res = subprocess.run( | |||||
| cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs) | |||||
| if res.returncode != 0: | if res.returncode != 0: | ||||
| print(f'Subprocess command {cmd} failed ' | print(f'Subprocess command {cmd} failed ' | ||||
| f'[{res.returncode}]:\n{res.stdout.decode()}') | f'[{res.returncode}]:\n{res.stdout.decode()}') | ||||
| def _clone_bootstrap_phase(ph: str) -> Path: | def _clone_bootstrap_phase(ph: str) -> Path: | ||||
| print(f'Cloning: {ph}') | |||||
| print(f'Clone revision: {ph}') | |||||
| bts_dir = BOOTSTRAP_BASE_DIR / ph | bts_dir = BOOTSTRAP_BASE_DIR / ph | ||||
| if bts_dir.exists(): | if bts_dir.exists(): | ||||
| shutil.rmtree(bts_dir) | shutil.rmtree(bts_dir) | ||||
| return bts_dir | return bts_dir | ||||
| def _build_bootstrap_phase(ph: str, bts_dir: Path, args: argparse.Namespace) -> None: | |||||
| print(f'Running build: {ph} (Please wait a moment...)') | |||||
| def _build_bootstrap_phase(ph: str, bts_dir: Path, | |||||
| args: argparse.Namespace) -> None: | |||||
| print(f'Build revision: {ph} [This may take a moment]') | |||||
| env = os.environ.copy() | env = os.environ.copy() | ||||
| env['DDS_BOOTSTRAP_PREV_EXE'] = str(PREBUILT_DIR / 'dds') | env['DDS_BOOTSTRAP_PREV_EXE'] = str(PREBUILT_DIR / 'dds') | ||||
| subprocess.check_call( | |||||
| _run_quiet( | |||||
| [ | [ | ||||
| sys.executable, | sys.executable, | ||||
| '-u', | '-u', | ||||
| parser.add_argument( | parser.add_argument( | ||||
| '--cxx', help='The C++ compiler to use for the build', required=True) | '--cxx', help='The C++ compiler to use for the build', required=True) | ||||
| args = parser.parse_args(argv) | args = parser.parse_args(argv) | ||||
| for phase in BOOTSTRAP_PHASES: | |||||
| for idx, phase in enumerate(BOOTSTRAP_PHASES): | |||||
| print(f'Bootstrap phase [{idx+1}/{len(BOOTSTRAP_PHASES)}]') | |||||
| exe = _run_boot_phase(phase, args) | exe = _run_boot_phase(phase, args) | ||||
| print(f'A bootstrapped DDS executable has been generated: {exe}') | print(f'A bootstrapped DDS executable has been generated: {exe}') |
| import argparse | import argparse | ||||
| import os | |||||
| import sys | import sys | ||||
| from pathlib import Path | from pathlib import Path | ||||
| from typing import Sequence, NamedTuple | from typing import Sequence, NamedTuple | ||||
| import subprocess | import subprocess | ||||
| import urllib.request | |||||
| HERE = Path(__file__).parent.absolute() | HERE = Path(__file__).parent.absolute() | ||||
| TOOLS_DIR = HERE | TOOLS_DIR = HERE | ||||
| class CIOptions(NamedTuple): | class CIOptions(NamedTuple): | ||||
| skip_bootstrap: bool | |||||
| cxx: Path | cxx: Path | ||||
| toolchain: str | toolchain: str | ||||
| def _do_bootstrap(opts: CIOptions) -> None: | |||||
| print('Running bootstrap') | |||||
| def _do_bootstrap_build(opts: CIOptions) -> None: | |||||
| print('Bootstrapping by a local build of prior versions...') | |||||
| subprocess.check_call([ | subprocess.check_call([ | ||||
| sys.executable, | sys.executable, | ||||
| '-u', | '-u', | ||||
| ]) | ]) | ||||
| def _do_bootstrap_download() -> None: | |||||
| filename = { | |||||
| 'win32': 'dds-win-x64.exe', | |||||
| 'linux': 'dds-linux-x64', | |||||
| 'darwin': 'dds-macos-x64', | |||||
| }.get(sys.platform) | |||||
| if filename is None: | |||||
| 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/bootstrap-p2/{filename}' | |||||
| print(f'Downloading prebuilt DDS executable: {url}') | |||||
| stream = urllib.request.urlopen(url) | |||||
| PREBUILT_DDS.parent.mkdir(exist_ok=True, parents=True) | |||||
| with PREBUILT_DDS.open('wb') as fd: | |||||
| while True: | |||||
| buf = stream.read(1024 * 4) | |||||
| if not buf: | |||||
| break | |||||
| fd.write(buf) | |||||
| if os.name != 'nt': | |||||
| # Mark the binary executable. By default it won't be | |||||
| mode = PREBUILT_DDS.stat().st_mode | |||||
| mode |= 0b001_001_001 | |||||
| PREBUILT_DDS.chmod(mode) | |||||
| def main(argv: Sequence[str]) -> int: | def main(argv: Sequence[str]) -> int: | ||||
| parser = argparse.ArgumentParser() | parser = argparse.ArgumentParser() | ||||
| parser.add_argument( | parser.add_argument( | ||||
| '--skip-bootstrap', | |||||
| action='store_true', | |||||
| '-B', | |||||
| '--bootstrap-with', | |||||
| help= | help= | ||||
| 'Skip the prebuild-bootstrap step. This requires a _prebuilt/dds to exist!', | 'Skip the prebuild-bootstrap step. This requires a _prebuilt/dds to exist!', | ||||
| choices=('download', 'build'), | |||||
| required=True, | |||||
| ) | ) | ||||
| parser.add_argument( | parser.add_argument( | ||||
| '--cxx', | '--cxx', | ||||
| help='The toolchain to use for the CI process', | help='The toolchain to use for the CI process', | ||||
| required=True) | required=True) | ||||
| args = parser.parse_args(argv) | args = parser.parse_args(argv) | ||||
| opts = CIOptions( | |||||
| skip_bootstrap=args.skip_bootstrap, | |||||
| cxx=Path(args.cxx), | |||||
| toolchain=args.toolchain) | |||||
| if not opts.skip_bootstrap: | |||||
| _do_bootstrap(opts) | |||||
| opts = CIOptions(cxx=Path(args.cxx), toolchain=args.toolchain) | |||||
| if args.bootstrap_with == 'build': | |||||
| _do_bootstrap_build(opts) | |||||
| elif args.bootstrap_with == 'download': | |||||
| _do_bootstrap_download() | |||||
| else: | |||||
| assert False, 'impossible' | |||||
| subprocess.check_call([ | subprocess.check_call([ | ||||
| str(PREBUILT_DDS), | str(PREBUILT_DDS), | ||||
| f'-T{opts.toolchain}', | f'-T{opts.toolchain}', | ||||
| ]) | ]) | ||||
| exe_suffix = '.exe' if os.name == 'nt' else '' | |||||
| subprocess.check_call([ | subprocess.check_call([ | ||||
| sys.executable, | sys.executable, | ||||
| '-u', | '-u', | ||||
| str(TOOLS_DIR / 'test.py'), | str(TOOLS_DIR / 'test.py'), | ||||
| f'--exe={PROJECT_ROOT / "_build/dds"}', | |||||
| f'--exe={PROJECT_ROOT / f"_build/dds{exe_suffix}"}', | |||||
| f'-T{opts.toolchain}', | f'-T{opts.toolchain}', | ||||
| ]) | ]) | ||||
| Compiler-ID: MSVC | Compiler-ID: MSVC | ||||
| C++-Version: C++17 | |||||
| Flags: /experimental:preprocessor /D FMT_HEADER_ONLY=1 /wd5105 | |||||
| # C++-Version: C++17 | |||||
| Flags: /experimental:preprocessor /D FMT_HEADER_ONLY=1 /wd5105 /std:c++latest | |||||
| Link-Flags: rpcrt4.lib | Link-Flags: rpcrt4.lib | ||||
| Optimize: True | Optimize: True |