From 463a16a68f84ee96b5baf18768739da04f0acdee Mon Sep 17 00:00:00 2001 From: Ivan Kravets Date: Wed, 16 Mar 2022 16:23:09 +0200 Subject: [PATCH] Implement "pio pkg uninstall" command // Issue #3373 --- docs | 2 +- platformio/commands/pkg.py | 2 + platformio/package/commands/uninstall.py | 239 +++++++++++++ platformio/package/manager/_uninstall.py | 2 +- platformio/package/manager/platform.py | 11 +- platformio/platform/_packages.py | 4 + tests/commands/pkg/test_uninstall.py | 416 +++++++++++++++++++++++ 7 files changed, 671 insertions(+), 5 deletions(-) create mode 100644 platformio/package/commands/uninstall.py create mode 100644 tests/commands/pkg/test_uninstall.py diff --git a/docs b/docs index 184bc075..7aa5b899 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 184bc075edb49faf02a92387edf44666cd99f772 +Subproject commit 7aa5b8996381e98c0824170378614fa3369a243e diff --git a/platformio/commands/pkg.py b/platformio/commands/pkg.py index c2454993..7e8ba0d0 100644 --- a/platformio/commands/pkg.py +++ b/platformio/commands/pkg.py @@ -19,6 +19,7 @@ from platformio.package.commands.install import package_install_cmd from platformio.package.commands.outdated import package_outdated_cmd from platformio.package.commands.pack import package_pack_cmd from platformio.package.commands.publish import package_publish_cmd +from platformio.package.commands.uninstall import package_uninstall_cmd from platformio.package.commands.unpublish import package_unpublish_cmd @@ -27,6 +28,7 @@ from platformio.package.commands.unpublish import package_unpublish_cmd commands=[ package_exec_cmd, package_install_cmd, + package_uninstall_cmd, package_outdated_cmd, package_pack_cmd, package_publish_cmd, diff --git a/platformio/package/commands/uninstall.py b/platformio/package/commands/uninstall.py new file mode 100644 index 00000000..b4391c6a --- /dev/null +++ b/platformio/package/commands/uninstall.py @@ -0,0 +1,239 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import os + +import click + +from platformio import fs +from platformio.package.manager.library import LibraryPackageManager +from platformio.package.manager.platform import PlatformPackageManager +from platformio.package.manager.tool import ToolPackageManager +from platformio.package.meta import PackageSpec +from platformio.project.config import ProjectConfig +from platformio.project.savedeps import pkg_to_save_spec, save_project_dependencies + + +@click.command( + "uninstall", short_help="Uninstall the project dependencies or custom packages" +) +@click.option( + "-d", + "--project-dir", + default=os.getcwd, + type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True), +) +@click.option("-e", "--environment", "environments", multiple=True) +@click.option("-p", "--platform", "platforms", multiple=True) +@click.option("-t", "--tool", "tools", multiple=True) +@click.option("-l", "--library", "libraries", multiple=True) +@click.option( + "--no-save", + is_flag=True, + help="Prevent removing specified packages from `platformio.ini`", +) +@click.option("--skip-dependencies", is_flag=True, help="Skip package dependencies") +@click.option("-g", "--global", is_flag=True, help="Uninstall global packages") +@click.option( + "--storage-dir", + default=None, + type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True), + help="Custom Package Manager storage for global packages", +) +@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting") +def package_uninstall_cmd(**options): + if options.get("global"): + uninstall_global_dependencies(options) + else: + uninstall_project_dependencies(options) + + +def uninstall_global_dependencies(options): + pm = PlatformPackageManager(options.get("storage_dir")) + tm = ToolPackageManager(options.get("storage_dir")) + lm = LibraryPackageManager(options.get("storage_dir")) + for obj in (pm, tm, lm): + obj.set_log_level(logging.WARN if options.get("silent") else logging.DEBUG) + for spec in options.get("platforms"): + pm.uninstall( + spec, + skip_dependencies=options.get("skip_dependencies"), + ) + for spec in options.get("tools"): + tm.uninstall( + spec, + skip_dependencies=options.get("skip_dependencies"), + ) + for spec in options.get("libraries", []): + lm.uninstall( + spec, + skip_dependencies=options.get("skip_dependencies"), + ) + + +def uninstall_project_dependencies(options): + environments = options["environments"] + with fs.cd(options["project_dir"]): + config = ProjectConfig.get_instance() + config.validate(environments) + for env in config.envs(): + if environments and env not in environments: + continue + if not options["silent"]: + click.echo( + "Resolving %s environment packages..." % click.style(env, fg="cyan") + ) + already_up_to_date = not uninstall_project_env_dependencies(env, options) + if not options["silent"] and already_up_to_date: + click.secho("Already up-to-date.", fg="green") + + +def uninstall_project_env_dependencies(project_env, options=None): + options = options or {} + uninstalled_conds = [] + # custom platforms + if options.get("platforms"): + uninstalled_conds.append( + _uninstall_project_env_custom_platforms(project_env, options) + ) + # custom tools + if options.get("tools"): + uninstalled_conds.append( + _uninstall_project_env_custom_tools(project_env, options) + ) + # custom ibraries + if options.get("libraries"): + uninstalled_conds.append( + _uninstall_project_env_custom_libraries(project_env, options) + ) + # declared dependencies + if not uninstalled_conds: + uninstalled_conds = [ + _uninstall_project_env_platform(project_env, options), + _uninstall_project_env_libraries(project_env, options), + ] + return any(uninstalled_conds) + + +def _uninstall_project_env_platform(project_env, options): + config = ProjectConfig.get_instance() + pm = PlatformPackageManager() + if options.get("silent"): + pm.set_log_level(logging.WARN) + spec = config.get(f"env:{project_env}", "platform") + if not spec: + return False + already_up_to_date = True + if not pm.get_package(spec): + return None + PlatformPackageManager().uninstall( + spec, + project_env=project_env, + skip_dependencies=options.get("skip_dependencies"), + ) + return not already_up_to_date + + +def _uninstall_project_env_custom_platforms(project_env, options): + already_up_to_date = True + pm = PlatformPackageManager() + if not options.get("silent"): + pm.set_log_level(logging.DEBUG) + for spec in options.get("platforms"): + if pm.get_package(spec): + already_up_to_date = False + pm.uninstall( + spec, + project_env=project_env, + skip_dependencies=options.get("skip_dependencies"), + ) + return not already_up_to_date + + +def _uninstall_project_env_custom_tools(project_env, options): + already_up_to_date = True + tm = ToolPackageManager() + if not options.get("silent"): + tm.set_log_level(logging.DEBUG) + specs_to_save = [] + for tool in options.get("tools"): + spec = PackageSpec(tool) + if tm.get_package(spec): + already_up_to_date = False + pkg = tm.uninstall( + spec, + skip_dependencies=options.get("skip_dependencies"), + ) + specs_to_save.append(pkg_to_save_spec(pkg, spec)) + if not options.get("no_save") and specs_to_save: + save_project_dependencies( + os.getcwd(), + specs_to_save, + scope="platform_packages", + action="remove", + environments=[project_env], + ) + return not already_up_to_date + + +def _uninstall_project_env_libraries(project_env, options): + already_up_to_date = True + config = ProjectConfig.get_instance() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), project_env) + ) + if options.get("silent"): + lm.set_log_level(logging.WARN) + for library in config.get(f"env:{project_env}", "lib_deps"): + spec = PackageSpec(library) + # skip built-in dependencies + if not spec.external and not spec.owner: + continue + if lm.get_package(spec): + already_up_to_date = False + lm.uninstall( + spec, + skip_dependencies=options.get("skip_dependencies"), + ) + return not already_up_to_date + + +def _uninstall_project_env_custom_libraries(project_env, options): + already_up_to_date = True + config = ProjectConfig.get_instance() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), project_env) + ) + if not options.get("silent"): + lm.set_log_level(logging.DEBUG) + specs_to_save = [] + for library in options.get("libraries") or []: + spec = PackageSpec(library) + if lm.get_package(spec): + already_up_to_date = False + pkg = lm.uninstall( + spec, + skip_dependencies=options.get("skip_dependencies"), + ) + specs_to_save.append(pkg_to_save_spec(pkg, spec)) + if not options.get("no_save") and specs_to_save: + save_project_dependencies( + os.getcwd(), + specs_to_save, + scope="lib_deps", + action="remove", + environments=[project_env], + ) + return not already_up_to_date diff --git a/platformio/package/manager/_uninstall.py b/platformio/package/manager/_uninstall.py index 76136844..032e4755 100644 --- a/platformio/package/manager/_uninstall.py +++ b/platformio/package/manager/_uninstall.py @@ -79,7 +79,7 @@ class PackageManagerUninstallMixin(object): dependencies = self.load_manifest(pkg).get("dependencies") if not dependencies: return - self.log.info(click.style("Removing dependencies...", fg="yellow")) + self.log.info("Removing dependencies...") for dependency in dependencies: spec = PackageSpec( owner=dependency.get("owner"), diff --git a/platformio/package/manager/platform.py b/platformio/package/manager/platform.py index 165f7ff2..6d6ece5f 100644 --- a/platformio/package/manager/platform.py +++ b/platformio/package/manager/platform.py @@ -79,18 +79,23 @@ class PlatformPackageManager(BasePackageManager): # pylint: disable=too-many-an p.on_installed() return pkg - def uninstall(self, spec, skip_dependencies=False): + def uninstall( # pylint: disable=arguments-differ + self, spec, skip_dependencies=False, project_env=None + ): pkg = self.get_package(spec) if not pkg or not pkg.metadata: raise UnknownPackageError(spec) p = PlatformFactory.new(pkg) # set logging level for underlying tool manager p.pm.set_log_level(self.log.getEffectiveLevel()) + if project_env: + p.configure_project_packages(project_env) + if not skip_dependencies: + p.uninstall_packages() assert super(PlatformPackageManager, self).uninstall( pkg, skip_dependencies=True ) - if not skip_dependencies: - p.on_uninstalled() + p.on_uninstalled() return pkg def update( # pylint: disable=arguments-differ, too-many-arguments diff --git a/platformio/platform/_packages.py b/platformio/platform/_packages.py index b0feebb2..95ecd402 100644 --- a/platformio/platform/_packages.py +++ b/platformio/platform/_packages.py @@ -114,6 +114,10 @@ class PlatformPackagesMixin(object): return result + def uninstall_packages(self): + for pkg in self.get_installed_packages(): + self.pm.uninstall(pkg) + def update_packages(self, only_check=False): for pkg in self.get_installed_packages(): self.pm.update( diff --git a/tests/commands/pkg/test_uninstall.py b/tests/commands/pkg/test_uninstall.py new file mode 100644 index 00000000..b7519451 --- /dev/null +++ b/tests/commands/pkg/test_uninstall.py @@ -0,0 +1,416 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# pylint: disable=unused-argument + +import os + +import pytest + +from platformio import fs +from platformio.package.commands.install import package_install_cmd +from platformio.package.commands.uninstall import package_uninstall_cmd +from platformio.package.manager.library import LibraryPackageManager +from platformio.package.manager.platform import PlatformPackageManager +from platformio.package.manager.tool import ToolPackageManager +from platformio.project.config import ProjectConfig + +PROJECT_CONFIG_TPL = """ +[env] +platform = platformio/atmelavr@^3.4.0 +lib_deps = milesburton/DallasTemperature@^3.9.1 + +[env:baremetal] +board = uno + +[env:devkit] +framework = arduino +board = attiny88 +""" + + +def pkgs_to_names(pkgs): + return [pkg.metadata.name for pkg in pkgs] + + +def test_global_packages( + clirunner, validate_cliresult, func_isolated_pio_core, tmp_path +): + # libraries + result = clirunner.invoke( + package_install_cmd, + [ + "--global", + "-l", + "marvinroger/Homie@^3.0.1", + ], + ) + validate_cliresult(result) + assert "Warning! Could not install dependency {'name': 'Hash'" in result.output + assert pkgs_to_names(LibraryPackageManager().get_installed()) == [ + "ArduinoJson", + "AsyncMqttClient", + "AsyncTCP", + "Bounce2", + "ESP Async WebServer", + "ESPAsyncTCP", + "Homie", + ] + # uninstall all deps + result = clirunner.invoke( + package_uninstall_cmd, + [ + "--global", + "-l", + "Homie", + ], + ) + validate_cliresult(result) + assert not pkgs_to_names(LibraryPackageManager().get_installed()) + + # skip dependencies + validate_cliresult( + clirunner.invoke( + package_install_cmd, + [ + "--global", + "-l", + "marvinroger/Homie@^3.0.1", + ], + ) + ) + result = clirunner.invoke( + package_uninstall_cmd, + ["--global", "-l", "marvinroger/Homie@^3.0.1", "--skip-dependencies"], + ) + validate_cliresult(result) + assert pkgs_to_names(LibraryPackageManager().get_installed()) == [ + "ArduinoJson", + "AsyncMqttClient", + "AsyncTCP", + "Bounce2", + "ESP Async WebServer", + "ESPAsyncTCP", + ] + # remove specific dependency + result = clirunner.invoke( + package_uninstall_cmd, + [ + "--global", + "-l", + "ESP Async WebServer", + ], + ) + validate_cliresult(result) + assert pkgs_to_names(LibraryPackageManager().get_installed()) == [ + "ArduinoJson", + "AsyncMqttClient", + "Bounce2", + ] + + # custom storage + storage_dir = tmp_path / "custom_lib_storage" + storage_dir.mkdir() + result = clirunner.invoke( + package_install_cmd, + [ + "--global", + "--storage-dir", + str(storage_dir), + "-l", + "marvinroger/Homie@^3.0.1", + "--skip-dependencies", + ], + ) + validate_cliresult(result) + assert pkgs_to_names(LibraryPackageManager(storage_dir).get_installed()) == [ + "Homie" + ] + result = clirunner.invoke( + package_uninstall_cmd, + [ + "--global", + "--storage-dir", + str(storage_dir), + "-l", + "marvinroger/Homie@^3.0.1", + ], + ) + validate_cliresult(result) + assert not pkgs_to_names(LibraryPackageManager(storage_dir).get_installed()) + + # tools + result = clirunner.invoke( + package_install_cmd, + ["--global", "-t", "platformio/framework-arduino-avr-attiny@^1.5.2"], + ) + validate_cliresult(result) + assert pkgs_to_names(ToolPackageManager().get_installed()) == [ + "framework-arduino-avr-attiny" + ] + result = clirunner.invoke( + package_uninstall_cmd, + ["--global", "-t", "framework-arduino-avr-attiny"], + ) + validate_cliresult(result) + assert not pkgs_to_names(ToolPackageManager().get_installed()) + + # platforms + result = clirunner.invoke( + package_install_cmd, + ["--global", "-p", "platformio/atmelavr@^3.4.0"], + ) + validate_cliresult(result) + assert pkgs_to_names(PlatformPackageManager().get_installed()) == ["atmelavr"] + assert pkgs_to_names(ToolPackageManager().get_installed()) == ["toolchain-atmelavr"] + result = clirunner.invoke( + package_uninstall_cmd, + ["--global", "-p", "platformio/atmelavr@^3.4.0"], + ) + validate_cliresult(result) + assert not pkgs_to_names(PlatformPackageManager().get_installed()) + assert not pkgs_to_names(ToolPackageManager().get_installed()) + + +def test_project(clirunner, validate_cliresult, isolated_pio_core, tmp_path): + project_dir = tmp_path / "project" + project_dir.mkdir() + (project_dir / "platformio.ini").write_text(PROJECT_CONFIG_TPL) + result = clirunner.invoke( + package_install_cmd, + ["-d", str(project_dir)], + ) + validate_cliresult(result) + with fs.cd(str(project_dir)): + config = ProjectConfig() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), "devkit") + ) + assert pkgs_to_names(lm.get_installed()) == ["DallasTemperature", "OneWire"] + assert pkgs_to_names(ToolPackageManager().get_installed()) == [ + "framework-arduino-avr-attiny", + "toolchain-atmelavr", + ] + assert config.get("env:devkit", "lib_deps") == [ + "milesburton/DallasTemperature@^3.9.1" + ] + + # try again + result = clirunner.invoke( + package_install_cmd, + ["-d", str(project_dir)], + ) + validate_cliresult(result) + assert "Already up-to-date" in result.output + + # uninstall + result = clirunner.invoke( + package_uninstall_cmd, + ["-d", str(project_dir)], + ) + validate_cliresult(result) + with fs.cd(str(project_dir)): + config = ProjectConfig() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), "devkit") + ) + assert not pkgs_to_names(lm.get_installed()) + assert not pkgs_to_names(ToolPackageManager().get_installed()) + assert config.get("env:devkit", "lib_deps") == [ + "milesburton/DallasTemperature@^3.9.1" + ] + + +def test_custom_project_libraries( + clirunner, validate_cliresult, func_isolated_pio_core, tmp_path +): + project_dir = tmp_path / "project" + project_dir.mkdir() + (project_dir / "platformio.ini").write_text(PROJECT_CONFIG_TPL) + spec = "bblanchon/ArduinoJson@^6.19.2" + result = clirunner.invoke( + package_install_cmd, + ["-d", str(project_dir), "-e", "devkit", "-l", spec], + ) + validate_cliresult(result) + assert "Already up-to-date" not in result.output + with fs.cd(str(project_dir)): + # check folders + config = ProjectConfig() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), "devkit") + ) + assert pkgs_to_names(lm.get_installed()) == ["ArduinoJson"] + # do not expect any platforms/tools + assert not os.path.exists(config.get("platformio", "platforms_dir")) + assert not os.path.exists(config.get("platformio", "packages_dir")) + # check saved deps + assert config.get("env:devkit", "lib_deps") == [ + "bblanchon/ArduinoJson@^6.19.2", + ] + # uninstall + result = clirunner.invoke( + package_uninstall_cmd, + ["-e", "devkit", "-l", spec], + ) + validate_cliresult(result) + config = ProjectConfig() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), "devkit") + ) + assert not pkgs_to_names(lm.get_installed()) + # do not expect any platforms/tools + assert not os.path.exists(config.get("platformio", "platforms_dir")) + assert not os.path.exists(config.get("platformio", "packages_dir")) + # check saved deps + assert config.get("env:devkit", "lib_deps") == [ + "milesburton/DallasTemperature@^3.9.1" + ] + + # install library without saving to config + result = clirunner.invoke( + package_install_cmd, + ["-e", "devkit", "-l", spec, "--no-save"], + ) + validate_cliresult(result) + config = ProjectConfig() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), "devkit") + ) + assert pkgs_to_names(lm.get_installed()) == ["ArduinoJson"] + assert config.get("env:devkit", "lib_deps") == [ + "milesburton/DallasTemperature@^3.9.1", + ] + result = clirunner.invoke( + package_uninstall_cmd, + ["-e", "devkit", "-l", spec, "--no-save"], + ) + validate_cliresult(result) + config = ProjectConfig() + assert config.get("env:devkit", "lib_deps") == [ + "milesburton/DallasTemperature@^3.9.1", + ] + + # unknown libraries + result = clirunner.invoke( + package_uninstall_cmd, ["-l", "platformio/unknown_library"] + ) + with pytest.raises(AssertionError, match="UnknownPackageError"): + validate_cliresult(result) + + +def test_custom_project_tools( + clirunner, validate_cliresult, func_isolated_pio_core, tmp_path +): + project_dir = tmp_path / "project" + project_dir.mkdir() + (project_dir / "platformio.ini").write_text(PROJECT_CONFIG_TPL) + spec = "platformio/tool-openocd" + result = clirunner.invoke( + package_install_cmd, + ["-d", str(project_dir), "-e", "devkit", "-t", spec], + ) + validate_cliresult(result) + with fs.cd(str(project_dir)): + config = ProjectConfig() + assert pkgs_to_names(ToolPackageManager().get_installed()) == ["tool-openocd"] + assert not LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), "devkit") + ).get_installed() + # do not expect any platforms + assert not os.path.exists(config.get("platformio", "platforms_dir")) + # check saved deps + assert config.get("env:devkit", "platform_packages") == [ + "platformio/tool-openocd@^2.1100.211028", + ] + # uninstall + result = clirunner.invoke( + package_uninstall_cmd, + ["-e", "devkit", "-t", spec], + ) + validate_cliresult(result) + assert not pkgs_to_names(ToolPackageManager().get_installed()) + # check saved deps + assert not ProjectConfig().get("env:devkit", "platform_packages") + + # install tool without saving to config + result = clirunner.invoke( + package_install_cmd, + ["-e", "devkit", "-t", "platformio/tool-esptoolpy@1.20310.0"], + ) + validate_cliresult(result) + assert pkgs_to_names(ToolPackageManager().get_installed()) == [ + "tool-esptoolpy", + ] + assert ProjectConfig().get("env:devkit", "platform_packages") == [ + "platformio/tool-esptoolpy@1.20310.0", + ] + # uninstall + result = clirunner.invoke( + package_uninstall_cmd, + ["-e", "devkit", "-t", "platformio/tool-esptoolpy@^1", "--no-save"], + ) + validate_cliresult(result) + assert not pkgs_to_names(ToolPackageManager().get_installed()) + assert ProjectConfig().get("env:devkit", "platform_packages") == [ + "platformio/tool-esptoolpy@1.20310.0", + ] + + # unknown tool + result = clirunner.invoke( + package_uninstall_cmd, ["-t", "platformio/unknown_tool"] + ) + with pytest.raises(AssertionError, match="UnknownPackageError"): + validate_cliresult(result) + + +def test_custom_project_platforms( + clirunner, validate_cliresult, func_isolated_pio_core, tmp_path +): + project_dir = tmp_path / "project" + project_dir.mkdir() + (project_dir / "platformio.ini").write_text(PROJECT_CONFIG_TPL) + spec = "platformio/atmelavr@^3.4.0" + result = clirunner.invoke( + package_install_cmd, + ["-d", str(project_dir), "-e", "devkit", "-p", spec], + ) + validate_cliresult(result) + with fs.cd(str(project_dir)): + config = ProjectConfig() + assert pkgs_to_names(PlatformPackageManager().get_installed()) == ["atmelavr"] + assert not LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), "devkit") + ).get_installed() + assert pkgs_to_names(ToolPackageManager().get_installed()) == [ + "framework-arduino-avr-attiny", + "toolchain-atmelavr", + ] + # uninstall + result = clirunner.invoke( + package_uninstall_cmd, + ["-e", "devkit", "-p", spec], + ) + validate_cliresult(result) + assert not pkgs_to_names(PlatformPackageManager().get_installed()) + assert not pkgs_to_names(ToolPackageManager().get_installed()) + + # unknown platform + with pytest.raises( + AssertionError, + match="Could not find the package with 'unknown_platform' requirements", + ): + validate_cliresult( + clirunner.invoke(package_uninstall_cmd, ["-p", "unknown_platform"]) + )