| namespace dds::cli::cmd { | namespace dds::cli::cmd { | ||||
| int sdist_create(const options& opts) { | |||||
| int pkg_create(const options& opts) { | |||||
| dds::sdist_params params{ | dds::sdist_params params{ | ||||
| .project_dir = opts.project_dir, | .project_dir = opts.project_dir, | ||||
| .dest_path = {}, | .dest_path = {}, |
| command build_deps; | command build_deps; | ||||
| command build; | command build; | ||||
| command compile_file; | command compile_file; | ||||
| command pkg_create; | |||||
| command pkg_get; | command pkg_get; | ||||
| command pkg_import; | command pkg_import; | ||||
| command pkg_ls; | command pkg_ls; | ||||
| command repoman_init; | command repoman_init; | ||||
| command repoman_ls; | command repoman_ls; | ||||
| command repoman_remove; | command repoman_remove; | ||||
| command sdist_create; | |||||
| } // namespace cmd | } // namespace cmd | ||||
| switch (opts.subcommand) { | switch (opts.subcommand) { | ||||
| case subcommand::build: | case subcommand::build: | ||||
| return cmd::build(opts); | return cmd::build(opts); | ||||
| case subcommand::sdist: { | |||||
| DDS_E_SCOPE(opts.sdist.subcommand); | |||||
| switch (opts.sdist.subcommand) { | |||||
| case sdist_subcommand::create: | |||||
| return cmd::sdist_create(opts); | |||||
| case sdist_subcommand::_none_:; | |||||
| } | |||||
| neo::unreachable(); | |||||
| } | |||||
| case subcommand::pkg: { | case subcommand::pkg: { | ||||
| DDS_E_SCOPE(opts.pkg.subcommand); | DDS_E_SCOPE(opts.pkg.subcommand); | ||||
| switch (opts.pkg.subcommand) { | switch (opts.pkg.subcommand) { | ||||
| case pkg_subcommand::ls: | case pkg_subcommand::ls: | ||||
| return cmd::pkg_ls(opts); | return cmd::pkg_ls(opts); | ||||
| case pkg_subcommand::create: | |||||
| return cmd::pkg_create(opts); | |||||
| case pkg_subcommand::get: | case pkg_subcommand::get: | ||||
| return cmd::pkg_get(opts); | return cmd::pkg_get(opts); | ||||
| case pkg_subcommand::import: | case pkg_subcommand::import: |
| .name = "pkg", | .name = "pkg", | ||||
| .help = "Manage packages and package remotes", | .help = "Manage packages and package remotes", | ||||
| })); | })); | ||||
| setup_sdist_cmd(group.add_parser({ | |||||
| .name = "sdist", | |||||
| .help = "Work with source distribution packages", | |||||
| })); | |||||
| setup_repoman_cmd(group.add_parser({ | setup_repoman_cmd(group.add_parser({ | ||||
| .name = "repoman", | .name = "repoman", | ||||
| .help = "Manage a dds package repository", | .help = "Manage a dds package repository", | ||||
| .valname = "<pkg-subcommand>", | .valname = "<pkg-subcommand>", | ||||
| .action = put_into(opts.pkg.subcommand), | .action = put_into(opts.pkg.subcommand), | ||||
| }); | }); | ||||
| setup_pkg_init_db_cmd(pkg_group.add_parser({ | |||||
| .name = "init-db", | |||||
| .help = "Initialize a new package database file (Path specified with '--pkg-db-path')", | |||||
| })); | |||||
| pkg_group.add_parser({ | pkg_group.add_parser({ | ||||
| .name = "ls", | .name = "ls", | ||||
| .help = "List locally available packages", | .help = "List locally available packages", | ||||
| }); | }); | ||||
| setup_pkg_create_cmd(pkg_group.add_parser({ | |||||
| .name = "create", | |||||
| .help = "Create a source distribution archive of a project", | |||||
| })); | |||||
| setup_pkg_get_cmd(pkg_group.add_parser({ | setup_pkg_get_cmd(pkg_group.add_parser({ | ||||
| .name = "get", | .name = "get", | ||||
| .help = "Obtain a copy of a package from a remote", | .help = "Obtain a copy of a package from a remote", | ||||
| })); | })); | ||||
| setup_pkg_init_db_cmd(pkg_group.add_parser({ | |||||
| .name = "init-db", | |||||
| .help = "Initialize a new package database file (Path specified with '--pkg-db-path')", | |||||
| })); | |||||
| setup_pkg_import_cmd(pkg_group.add_parser({ | setup_pkg_import_cmd(pkg_group.add_parser({ | ||||
| .name = "import", | .name = "import", | ||||
| .help = "Import a source distribution archive into the local package cache", | .help = "Import a source distribution archive into the local package cache", | ||||
| })); | })); | ||||
| } | } | ||||
| void setup_pkg_create_cmd(argument_parser& pkg_create_cmd) { | |||||
| pkg_create_cmd.add_argument(project_arg.dup()).help | |||||
| = "Path to the project for which to create a source distribution.\n" | |||||
| "Default is the current working directory."; | |||||
| pkg_create_cmd.add_argument(out_arg.dup()).help | |||||
| = "Destination path for the source distributioon archive"; | |||||
| pkg_create_cmd.add_argument(if_exists_arg.dup()).help | |||||
| = "What to do if the destination names an existing file"; | |||||
| } | |||||
| void setup_pkg_get_cmd(argument_parser& pkg_get_cmd) { | void setup_pkg_get_cmd(argument_parser& pkg_get_cmd) { | ||||
| pkg_get_cmd.add_argument({ | pkg_get_cmd.add_argument({ | ||||
| .valname = "<pkg-id>", | .valname = "<pkg-id>", | ||||
| }); | }); | ||||
| } | } | ||||
| void setup_sdist_cmd(argument_parser& sdist_cmd) noexcept { | |||||
| auto& sdist_grp = sdist_cmd.add_subparsers({ | |||||
| .valname = "<sdist-subcommand>", | |||||
| .action = put_into(opts.sdist.subcommand), | |||||
| }); | |||||
| setup_sdist_create_cmd(sdist_grp.add_parser({ | |||||
| .name = "create", | |||||
| .help = "Create a source distribution from a project tree", | |||||
| })); | |||||
| } | |||||
| void setup_sdist_create_cmd(argument_parser& sdist_create_cmd) { | |||||
| sdist_create_cmd.add_argument(project_arg.dup()).help | |||||
| = "Path to the project for which to create a source distribution.\n" | |||||
| "Default is the current working directory."; | |||||
| sdist_create_cmd.add_argument(out_arg.dup()).help | |||||
| = "Destination path for the source distributnion archive"; | |||||
| sdist_create_cmd.add_argument(if_exists_arg.dup()).help | |||||
| = "What to do if the destination names an existing file"; | |||||
| } | |||||
| void setup_repoman_cmd(argument_parser& repoman_cmd) { | void setup_repoman_cmd(argument_parser& repoman_cmd) { | ||||
| auto& grp = repoman_cmd.add_subparsers({ | auto& grp = repoman_cmd.add_subparsers({ | ||||
| .valname = "<repoman-subcommand>", | .valname = "<repoman-subcommand>", |
| compile_file, | compile_file, | ||||
| build_deps, | build_deps, | ||||
| pkg, | pkg, | ||||
| sdist, | |||||
| repoman, | repoman, | ||||
| }; | }; | ||||
| /** | |||||
| * @brief 'dds sdist' subcommands | |||||
| */ | |||||
| enum class sdist_subcommand { | |||||
| _none_, | |||||
| create, | |||||
| }; | |||||
| /** | /** | ||||
| * @brief 'dds pkg' subcommands | * @brief 'dds pkg' subcommands | ||||
| */ | */ | ||||
| _none_, | _none_, | ||||
| ls, | ls, | ||||
| get, | get, | ||||
| create, | |||||
| import, | import, | ||||
| repo, | repo, | ||||
| search, | search, | ||||
| } search; | } search; | ||||
| } pkg; | } pkg; | ||||
| struct { | |||||
| sdist_subcommand subcommand; | |||||
| } sdist; | |||||
| /** | /** | ||||
| * @brief Parameters for 'dds repoman' | * @brief Parameters for 'dds repoman' | ||||
| */ | */ |
| def test_empty_sdist_create(tmp_project: Project) -> None: | def test_empty_sdist_create(tmp_project: Project) -> None: | ||||
| tmp_project.package_json = TEST_PACKAGE | tmp_project.package_json = TEST_PACKAGE | ||||
| tmp_project.sdist_create() | |||||
| tmp_project.pkg_create() | |||||
| assert tmp_project.build_root.joinpath('test-pkg@0.2.2.tar.gz').is_file(), \ | assert tmp_project.build_root.joinpath('test-pkg@0.2.2.tar.gz').is_file(), \ | ||||
| 'The expected sdist tarball was not generated' | 'The expected sdist tarball was not generated' |
| @pytest.fixture() | @pytest.fixture() | ||||
| def test_project(project_opener: ProjectOpener) -> Project: | def test_project(project_opener: ProjectOpener) -> Project: | ||||
| return project_opener.open('projects/sdist') | |||||
| return project_opener.open('projects/simple') | |||||
| def test_create_sdist(test_project: Project, tmp_path: Path) -> None: | |||||
| def test_create_pkg(test_project: Project, tmp_path: Path) -> None: | |||||
| # Create in the default location | # Create in the default location | ||||
| test_project.sdist_create() | |||||
| test_project.pkg_create() | |||||
| sd_dir = test_project.build_root / 'foo@1.2.3.tar.gz' | sd_dir = test_project.build_root / 'foo@1.2.3.tar.gz' | ||||
| assert sd_dir.is_file(), 'Did not create an sdist in the default location' | assert sd_dir.is_file(), 'Did not create an sdist in the default location' | ||||
| # Create in a different location | # Create in a different location | ||||
| dest = tmp_path / 'dummy.tar.gz' | dest = tmp_path / 'dummy.tar.gz' | ||||
| test_project.sdist_create(dest=dest) | |||||
| test_project.pkg_create(dest=dest) | |||||
| assert dest.is_file(), 'Did not create an sdist in the new location' | assert dest.is_file(), 'Did not create an sdist in the new location' | ||||
| @pytest.fixture() | @pytest.fixture() | ||||
| def test_sdist(test_project: Project) -> Tuple[Path, Project]: | |||||
| def _test_pkg(test_project: Project) -> Tuple[Path, Project]: | |||||
| repo_content_path = test_project.dds.repo_dir / 'foo@1.2.3' | repo_content_path = test_project.dds.repo_dir / 'foo@1.2.3' | ||||
| assert not repo_content_path.is_dir() | assert not repo_content_path.is_dir() | ||||
| test_project.sdist_create() | |||||
| test_project.pkg_create() | |||||
| assert not repo_content_path.is_dir() | assert not repo_content_path.is_dir() | ||||
| return test_project.build_root / 'foo@1.2.3.tar.gz', test_project | return test_project.build_root / 'foo@1.2.3.tar.gz', test_project | ||||
| def test_import_sdist_archive(test_sdist: Tuple[Path, Project]) -> None: | |||||
| sdist, project = test_sdist | |||||
| def test_import_sdist_archive(_test_pkg: Tuple[Path, Project]) -> None: | |||||
| sdist, project = _test_pkg | |||||
| repo_content_path = project.dds.repo_dir / 'foo@1.2.3' | repo_content_path = project.dds.repo_dir / 'foo@1.2.3' | ||||
| project.dds.pkg_import(sdist) | project.dds.pkg_import(sdist) | ||||
| assert repo_content_path.is_dir(), \ | assert repo_content_path.is_dir(), \ | ||||
| 'Non-package content appeared in the package cache' | 'Non-package content appeared in the package cache' | ||||
| def test_import_sdist_stdin(test_sdist: Tuple[Path, Project]) -> None: | |||||
| sdist, project = test_sdist | |||||
| def test_import_sdist_stdin(_test_pkg: Tuple[Path, Project]) -> None: | |||||
| sdist, project = _test_pkg | |||||
| repo_content_path = project.dds.repo_dir / 'foo@1.2.3' | repo_content_path = project.dds.repo_dir / 'foo@1.2.3' | ||||
| pipe = subprocess.Popen( | pipe = subprocess.Popen( | ||||
| list(proc.flatten_cmd([ | list(proc.flatten_cmd([ | ||||
| rc = pipe.wait() | rc = pipe.wait() | ||||
| assert rc == 0, 'Subprocess failed' | assert rc == 0, 'Subprocess failed' | ||||
| # project.dds.pkg_import(sdist) | |||||
| assert repo_content_path.is_dir(), \ | assert repo_content_path.is_dir(), \ | ||||
| 'The package did not appear in the local cache' | 'The package did not appear in the local cache' | ||||
| assert repo_content_path.joinpath('library.jsonc').is_file(), \ | assert repo_content_path.joinpath('library.jsonc').is_file(), \ | ||||
| def test_sdist_invalid_project(tmp_project: Project) -> None: | def test_sdist_invalid_project(tmp_project: Project) -> None: | ||||
| with error.expect_error_marker('no-package-json5'): | with error.expect_error_marker('no-package-json5'): | ||||
| tmp_project.sdist_create() | |||||
| tmp_project.pkg_create() | |||||
| @pytest.mark.skipif(platform.system() != 'Linux', reason='We know this fails on Linux') | @pytest.mark.skipif(platform.system() != 'Linux', reason='We know this fails on Linux') | ||||
| def test_sdist_unreadable_dir(dds: DDSWrapper) -> None: | def test_sdist_unreadable_dir(dds: DDSWrapper) -> None: | ||||
| with error.expect_error_marker('failed-package-json5-scan'): | with error.expect_error_marker('failed-package-json5-scan'): | ||||
| dds.run(['sdist', 'create', '--project=/root']) | |||||
| dds.run(['pkg', 'create', '--project=/root']) | |||||
| def test_sdist_invalid_json5(tmp_project: Project) -> None: | def test_sdist_invalid_json5(tmp_project: Project) -> None: | ||||
| tmp_project.write('package.json5', 'bogus json5') | tmp_project.write('package.json5', 'bogus json5') | ||||
| with error.expect_error_marker('package-json5-parse-error'): | with error.expect_error_marker('package-json5-parse-error'): | ||||
| tmp_project.sdist_create() | |||||
| tmp_project.pkg_create() |
| with tc_mod.fixup_toolchain(toolchain or tc_mod.get_default_test_toolchain()) as tc: | with tc_mod.fixup_toolchain(toolchain or tc_mod.get_default_test_toolchain()) as tc: | ||||
| self.dds.compile_file(paths, toolchain=tc, out=self.build_root, project_dir=self.root) | self.dds.compile_file(paths, toolchain=tc, out=self.build_root, project_dir=self.root) | ||||
| def sdist_create(self, *, dest: Optional[Pathish] = None) -> None: | |||||
| def pkg_create(self, *, dest: Optional[Pathish] = None) -> None: | |||||
| self.build_root.mkdir(exist_ok=True, parents=True) | self.build_root.mkdir(exist_ok=True, parents=True) | ||||
| self.dds.run([ | self.dds.run([ | ||||
| 'sdist', | |||||
| 'pkg', | |||||
| 'create', | 'create', | ||||
| self.project_dir_arg, | self.project_dir_arg, | ||||
| f'--out={dest}' if dest else (), | f'--out={dest}' if dest else (), |
| def spec_as_local_tgz(dds_exe: Path, spec: SpecPackage) -> Iterator[Path]: | def spec_as_local_tgz(dds_exe: Path, spec: SpecPackage) -> Iterator[Path]: | ||||
| with spec.remote.make_local_dir(spec.name, spec.version) as clone_dir: | with spec.remote.make_local_dir(spec.name, spec.version) as clone_dir: | ||||
| out_tgz = clone_dir / 'sdist.tgz' | out_tgz = clone_dir / 'sdist.tgz' | ||||
| check_call([str(dds_exe), 'sdist', 'create', f'--project={clone_dir}', f'--out={out_tgz}']) | |||||
| check_call([str(dds_exe), 'pkg', 'create', f'--project={clone_dir}', f'--out={out_tgz}']) | |||||
| yield out_tgz | yield out_tgz | ||||