tan-cli 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tan_cli-0.7.0/PKG-INFO +125 -0
- tan_cli-0.7.0/README.md +83 -0
- tan_cli-0.7.0/pyproject.toml +293 -0
- tan_cli-0.7.0/setup.cfg +4 -0
- tan_cli-0.7.0/tan/__init__.py +0 -0
- tan_cli-0.7.0/tan/__main__.py +120 -0
- tan_cli-0.7.0/tan/cli.py +939 -0
- tan_cli-0.7.0/tan/command_table.py +265 -0
- tan_cli-0.7.0/tan/commands/__init__.py +8 -0
- tan_cli-0.7.0/tan/commands/bootstrap_cmd.py +4856 -0
- tan_cli-0.7.0/tan/commands/bootstrap_patches.py +164 -0
- tan_cli-0.7.0/tan/commands/bootstrap_toolchain_fetch.py +155 -0
- tan_cli-0.7.0/tan/commands/bootstrap_toolchain_pin.py +36 -0
- tan_cli-0.7.0/tan/commands/build/__init__.py +1 -0
- tan_cli-0.7.0/tan/commands/build/configure_inputs.py +277 -0
- tan_cli-0.7.0/tan/commands/build/execute.py +1852 -0
- tan_cli-0.7.0/tan/commands/build/host_python.py +59 -0
- tan_cli-0.7.0/tan/commands/build/link_stale.py +78 -0
- tan_cli-0.7.0/tan/commands/build/manifest.py +428 -0
- tan_cli-0.7.0/tan/commands/build/materialise.py +81 -0
- tan_cli-0.7.0/tan/commands/build/token_substitution.py +381 -0
- tan_cli-0.7.0/tan/commands/build/toolchain.py +482 -0
- tan_cli-0.7.0/tan/commands/build/user_defines_wipe.py +115 -0
- tan_cli-0.7.0/tan/commands/build/workspace_patches.py +65 -0
- tan_cli-0.7.0/tan/commands/build_cmd.py +2236 -0
- tan_cli-0.7.0/tan/commands/build_output.py +331 -0
- tan_cli-0.7.0/tan/commands/clean_cmd.py +1183 -0
- tan_cli-0.7.0/tan/commands/completion_cmd.py +786 -0
- tan_cli-0.7.0/tan/commands/debug_config_cmd.py +2425 -0
- tan_cli-0.7.0/tan/commands/diff_cmd.py +940 -0
- tan_cli-0.7.0/tan/commands/doctor_cmd.py +4767 -0
- tan_cli-0.7.0/tan/commands/examples_cmd.py +644 -0
- tan_cli-0.7.0/tan/commands/explain_cmd.py +1319 -0
- tan_cli-0.7.0/tan/commands/faultdecode_cmd.py +931 -0
- tan_cli-0.7.0/tan/commands/flash_atoc_guard.py +240 -0
- tan_cli-0.7.0/tan/commands/flash_cmd.py +5731 -0
- tan_cli-0.7.0/tan/commands/flash_mram_guard.py +150 -0
- tan_cli-0.7.0/tan/commands/flash_ram.py +578 -0
- tan_cli-0.7.0/tan/commands/flash_raw.py +583 -0
- tan_cli-0.7.0/tan/commands/generate_cmd.py +1456 -0
- tan_cli-0.7.0/tan/commands/image_cmd.py +753 -0
- tan_cli-0.7.0/tan/commands/init_cmd.py +1740 -0
- tan_cli-0.7.0/tan/commands/inspect_cmd.py +377 -0
- tan_cli-0.7.0/tan/commands/kconfig_cmd.py +734 -0
- tan_cli-0.7.0/tan/commands/model_cmd.py +1514 -0
- tan_cli-0.7.0/tan/commands/model_device_cmd.py +351 -0
- tan_cli-0.7.0/tan/commands/model_device_live.py +234 -0
- tan_cli-0.7.0/tan/commands/model_host_cmd.py +430 -0
- tan_cli-0.7.0/tan/commands/model_zoo_cmd.py +363 -0
- tan_cli-0.7.0/tan/commands/monitor_actions.py +125 -0
- tan_cli-0.7.0/tan/commands/monitor_cmd.py +626 -0
- tan_cli-0.7.0/tan/commands/monitor_session.py +505 -0
- tan_cli-0.7.0/tan/commands/new_som_cmd.py +1522 -0
- tan_cli-0.7.0/tan/commands/pinmux_cmd.py +568 -0
- tan_cli-0.7.0/tan/commands/presets_cmd.py +1123 -0
- tan_cli-0.7.0/tan/commands/probe_cmd.py +421 -0
- tan_cli-0.7.0/tan/commands/reset_cmd.py +354 -0
- tan_cli-0.7.0/tan/commands/reset_confirm.py +168 -0
- tan_cli-0.7.0/tan/commands/run_cmd.py +637 -0
- tan_cli-0.7.0/tan/commands/scaffold_cmd.py +490 -0
- tan_cli-0.7.0/tan/commands/sdk_cmd.py +1723 -0
- tan_cli-0.7.0/tan/commands/size_cmd.py +1020 -0
- tan_cli-0.7.0/tan/commands/support_bundle_cmd.py +1073 -0
- tan_cli-0.7.0/tan/commands/trace_cmd.py +341 -0
- tan_cli-0.7.0/tan/commands/validate_cmd.py +2009 -0
- tan_cli-0.7.0/tan/commands/west_forward_cmd.py +736 -0
- tan_cli-0.7.0/tan/commands/workspace_patch_check.py +296 -0
- tan_cli-0.7.0/tan/core/__init__.py +1 -0
- tan_cli-0.7.0/tan/core/alp_conf_pregen.py +100 -0
- tan_cli-0.7.0/tan/core/artifact_provenance.py +240 -0
- tan_cli-0.7.0/tan/core/atoc_guard.py +276 -0
- tan_cli-0.7.0/tan/core/atoc_guard_messages.py +232 -0
- tan_cli-0.7.0/tan/core/atoc_replacement.py +118 -0
- tan_cli-0.7.0/tan/core/atomic_write.py +178 -0
- tan_cli-0.7.0/tan/core/board_context.py +351 -0
- tan_cli-0.7.0/tan/core/board_diagnostic.py +111 -0
- tan_cli-0.7.0/tan/core/board_files.py +221 -0
- tan_cli-0.7.0/tan/core/board_validator.py +542 -0
- tan_cli-0.7.0/tan/core/board_validator_compat.py +196 -0
- tan_cli-0.7.0/tan/core/board_validator_run.py +129 -0
- tan_cli-0.7.0/tan/core/board_validator_schema.py +186 -0
- tan_cli-0.7.0/tan/core/board_validator_skew.py +46 -0
- tan_cli-0.7.0/tan/core/board_yaml_edit.py +295 -0
- tan_cli-0.7.0/tan/core/board_yaml_pos.py +118 -0
- tan_cli-0.7.0/tan/core/bootstrap.py +2572 -0
- tan_cli-0.7.0/tan/core/build_plan.py +625 -0
- tan_cli-0.7.0/tan/core/consent.py +104 -0
- tan_cli-0.7.0/tan/core/console_filter.py +245 -0
- tan_cli-0.7.0/tan/core/debug_launch.py +1803 -0
- tan_cli-0.7.0/tan/core/devicetree_lint.py +168 -0
- tan_cli-0.7.0/tan/core/dir_removal.py +229 -0
- tan_cli-0.7.0/tan/core/doctor_git.py +172 -0
- tan_cli-0.7.0/tan/core/doctor_libraries.py +253 -0
- tan_cli-0.7.0/tan/core/doctor_render.py +185 -0
- tan_cli-0.7.0/tan/core/doctor_scope.py +66 -0
- tan_cli-0.7.0/tan/core/doctor_setools.py +237 -0
- tan_cli-0.7.0/tan/core/doctor_stale.py +180 -0
- tan_cli-0.7.0/tan/core/document_guards.py +723 -0
- tan_cli-0.7.0/tan/core/dp_id.py +97 -0
- tan_cli-0.7.0/tan/core/elf_load.py +63 -0
- tan_cli-0.7.0/tan/core/error_catalog.py +171 -0
- tan_cli-0.7.0/tan/core/example_catalog.py +499 -0
- tan_cli-0.7.0/tan/core/example_facets.py +151 -0
- tan_cli-0.7.0/tan/core/faultdecode.py +521 -0
- tan_cli-0.7.0/tan/core/flash_plan.py +2501 -0
- tan_cli-0.7.0/tan/core/flow_d_report.py +381 -0
- tan_cli-0.7.0/tan/core/flow_d_uptime.py +164 -0
- tan_cli-0.7.0/tan/core/fs_confine.py +130 -0
- tan_cli-0.7.0/tan/core/global_flags.py +230 -0
- tan_cli-0.7.0/tan/core/host_python.py +210 -0
- tan_cli-0.7.0/tan/core/image_bundle.py +247 -0
- tan_cli-0.7.0/tan/core/inert.py +147 -0
- tan_cli-0.7.0/tan/core/jlink_binary.py +128 -0
- tan_cli-0.7.0/tan/core/jlink_probe.py +481 -0
- tan_cli-0.7.0/tan/core/jsonc_splice.py +404 -0
- tan_cli-0.7.0/tan/core/launch_provenance.py +257 -0
- tan_cli-0.7.0/tan/core/link_refusal.py +76 -0
- tan_cli-0.7.0/tan/core/metadata_schema.py +376 -0
- tan_cli-0.7.0/tan/core/model_check.py +227 -0
- tan_cli-0.7.0/tan/core/model_device.py +449 -0
- tan_cli-0.7.0/tan/core/model_doctor.py +198 -0
- tan_cli-0.7.0/tan/core/model_host.py +48 -0
- tan_cli-0.7.0/tan/core/model_list.py +112 -0
- tan_cli-0.7.0/tan/core/model_zoo.py +353 -0
- tan_cli-0.7.0/tan/core/module_template.py +340 -0
- tan_cli-0.7.0/tan/core/mram_link.py +87 -0
- tan_cli-0.7.0/tan/core/new_som_ospi.py +94 -0
- tan_cli-0.7.0/tan/core/os_class.py +159 -0
- tan_cli-0.7.0/tan/core/patch_paths.py +87 -0
- tan_cli-0.7.0/tan/core/pending.py +40 -0
- tan_cli-0.7.0/tan/core/plain_zephyr_plan.py +138 -0
- tan_cli-0.7.0/tan/core/plan_exec.py +373 -0
- tan_cli-0.7.0/tan/core/plan_tokens.py +600 -0
- tan_cli-0.7.0/tan/core/probe.py +155 -0
- tan_cli-0.7.0/tan/core/probe_plan.py +237 -0
- tan_cli-0.7.0/tan/core/proxy.py +168 -0
- tan_cli-0.7.0/tan/core/publish.py +31 -0
- tan_cli-0.7.0/tan/core/python_floor.py +178 -0
- tan_cli-0.7.0/tan/core/ram_run.py +694 -0
- tan_cli-0.7.0/tan/core/ram_watch.py +231 -0
- tan_cli-0.7.0/tan/core/raw_write.py +160 -0
- tan_cli-0.7.0/tan/core/reset_plan.py +56 -0
- tan_cli-0.7.0/tan/core/run.py +161 -0
- tan_cli-0.7.0/tan/core/scaffold.py +2203 -0
- tan_cli-0.7.0/tan/core/scaffold_selftest_identity.py +225 -0
- tan_cli-0.7.0/tan/core/sdk_default_registry.py +565 -0
- tan_cli-0.7.0/tan/core/sdk_discovery.py +996 -0
- tan_cli-0.7.0/tan/core/sdk_removal.py +465 -0
- tan_cli-0.7.0/tan/core/serial_actions.py +175 -0
- tan_cli-0.7.0/tan/core/serial_capture.py +185 -0
- tan_cli-0.7.0/tan/core/serial_url.py +45 -0
- tan_cli-0.7.0/tan/core/setools.py +608 -0
- tan_cli-0.7.0/tan/core/setools_scratch.py +243 -0
- tan_cli-0.7.0/tan/core/shapes.py +194 -0
- tan_cli-0.7.0/tan/core/size.py +610 -0
- tan_cli-0.7.0/tan/core/som_buildability.py +204 -0
- tan_cli-0.7.0/tan/core/som_schema_version.py +62 -0
- tan_cli-0.7.0/tan/core/subprocess_env.py +201 -0
- tan_cli-0.7.0/tan/core/sysbuild_image.py +94 -0
- tan_cli-0.7.0/tan/core/system_manifest.py +827 -0
- tan_cli-0.7.0/tan/core/text_layout.py +73 -0
- tan_cli-0.7.0/tan/core/timestamp.py +182 -0
- tan_cli-0.7.0/tan/core/tool_lookup.py +383 -0
- tan_cli-0.7.0/tan/core/toolchain_pin.py +117 -0
- tan_cli-0.7.0/tan/core/toolchain_provision.py +1224 -0
- tan_cli-0.7.0/tan/core/trusted_path.py +134 -0
- tan_cli-0.7.0/tan/core/uboot_breakin.py +154 -0
- tan_cli-0.7.0/tan/core/uri_reference.py +415 -0
- tan_cli-0.7.0/tan/core/user_defines.py +152 -0
- tan_cli-0.7.0/tan/core/venv.py +332 -0
- tan_cli-0.7.0/tan/core/west_patches.py +143 -0
- tan_cli-0.7.0/tan/core/west_workspace_refusal.py +159 -0
- tan_cli-0.7.0/tan/core/zephyr_env.py +124 -0
- tan_cli-0.7.0/tan/env.py +185 -0
- tan_cli-0.7.0/tan/envelope.py +647 -0
- tan_cli-0.7.0/tan/exit_codes.py +12 -0
- tan_cli-0.7.0/tan/lazy_typer.py +185 -0
- tan_cli-0.7.0/tan/model/__init__.py +2 -0
- tan_cli-0.7.0/tan/model/_gen_fixture.py +132 -0
- tan_cli-0.7.0/tan/model/adapters/__init__.py +116 -0
- tan_cli-0.7.0/tan/model/adapters/cpu.py +26 -0
- tan_cli-0.7.0/tan/model/adapters/deepx.py +94 -0
- tan_cli-0.7.0/tan/model/adapters/drpai.py +242 -0
- tan_cli-0.7.0/tan/model/adapters/ethos_u.py +951 -0
- tan_cli-0.7.0/tan/model/adapters/executorch.py +55 -0
- tan_cli-0.7.0/tan/model/analyze.py +694 -0
- tan_cli-0.7.0/tan/model/build.py +304 -0
- tan_cli-0.7.0/tan/model/check.py +682 -0
- tan_cli-0.7.0/tan/model/manifest.py +625 -0
- tan_cli-0.7.0/tan/model/measure.py +216 -0
- tan_cli-0.7.0/tan/model/npu_ops_validate.py +130 -0
- tan_cli-0.7.0/tan/model/package.py +125 -0
- tan_cli-0.7.0/tan/model/perf.py +635 -0
- tan_cli-0.7.0/tan/model/perf_apply.py +721 -0
- tan_cli-0.7.0/tan/model/prep.py +193 -0
- tan_cli-0.7.0/tan/model/sram_fit.py +502 -0
- tan_cli-0.7.0/tan/model/targets.py +374 -0
- tan_cli-0.7.0/tan/model/tensorio.py +287 -0
- tan_cli-0.7.0/tan/net.py +73 -0
- tan_cli-0.7.0/tan/output_format.py +213 -0
- tan_cli-0.7.0/tan/planner/__init__.py +219 -0
- tan_cli-0.7.0/tan/planner/aperture.py +202 -0
- tan_cli-0.7.0/tan/planner/buildplan.py +1092 -0
- tan_cli-0.7.0/tan/planner/camera_owner.py +192 -0
- tan_cli-0.7.0/tan/planner/cameras.py +132 -0
- tan_cli-0.7.0/tan/planner/carveout.py +755 -0
- tan_cli-0.7.0/tan/planner/cli.py +274 -0
- tan_cli-0.7.0/tan/planner/headers.py +315 -0
- tan_cli-0.7.0/tan/planner/kconfig.py +2435 -0
- tan_cli-0.7.0/tan/planner/kconfig_symbols.py +473 -0
- tan_cli-0.7.0/tan/planner/libraries.py +677 -0
- tan_cli-0.7.0/tan/planner/link_target.py +237 -0
- tan_cli-0.7.0/tan/planner/linux_ownership.py +216 -0
- tan_cli-0.7.0/tan/planner/loader.py +1525 -0
- tan_cli-0.7.0/tan/planner/manifest.py +116 -0
- tan_cli-0.7.0/tan/planner/memory.py +161 -0
- tan_cli-0.7.0/tan/planner/memregion.py +56 -0
- tan_cli-0.7.0/tan/planner/models.py +533 -0
- tan_cli-0.7.0/tan/planner/orchestrator.py +874 -0
- tan_cli-0.7.0/tan/planner/ownership.py +203 -0
- tan_cli-0.7.0/tan/planner/partition.py +1160 -0
- tan_cli-0.7.0/tan/planner/paths.py +32 -0
- tan_cli-0.7.0/tan/planner/plain_slice.py +139 -0
- tan_cli-0.7.0/tan/planner/project_emit/__init__.py +60 -0
- tan_cli-0.7.0/tan/planner/project_emit/bom_netlist.py +375 -0
- tan_cli-0.7.0/tan/planner/project_emit/dts.py +580 -0
- tan_cli-0.7.0/tan/planner/project_emit/hw_info.py +221 -0
- tan_cli-0.7.0/tan/planner/project_emit/native_sim.py +136 -0
- tan_cli-0.7.0/tan/planner/project_emit/west_libs.py +213 -0
- tan_cli-0.7.0/tan/planner/project_loader.py +264 -0
- tan_cli-0.7.0/tan/planner/sdk_capability.py +203 -0
- tan_cli-0.7.0/tan/planner/sdk_compat.py +395 -0
- tan_cli-0.7.0/tan/planner/secure.py +350 -0
- tan_cli-0.7.0/tan/planner/slugs.py +394 -0
- tan_cli-0.7.0/tan/planner/som_metadata.py +325 -0
- tan_cli-0.7.0/tan/planner/strict_loaders.py +131 -0
- tan_cli-0.7.0/tan/planner/template.py +1037 -0
- tan_cli-0.7.0/tan/planner/template_pins.py +535 -0
- tan_cli-0.7.0/tan/planner/template_rewrite.py +714 -0
- tan_cli-0.7.0/tan/planner/topology.py +208 -0
- tan_cli-0.7.0/tan/planner/validate.py +365 -0
- tan_cli-0.7.0/tan/planner/whole_device_alias.py +67 -0
- tan_cli-0.7.0/tan/planner/zephyr_board.py +3510 -0
- tan_cli-0.7.0/tan/planner_cli.py +97 -0
- tan_cli-0.7.0/tan/planner_emit.py +617 -0
- tan_cli-0.7.0/tan/planner_root.py +119 -0
- tan_cli-0.7.0/tan/soc_ref.py +58 -0
- tan_cli-0.7.0/tan/templates/__init__.py +34 -0
- tan_cli-0.7.0/tan/templates/vendored/MANIFEST.md +1862 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
- tan_cli-0.7.0/tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
- tan_cli-0.7.0/tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
- tan_cli-0.7.0/tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
- tan_cli-0.7.0/tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
- tan_cli-0.7.0/tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
- tan_cli-0.7.0/tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
- tan_cli-0.7.0/tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
- tan_cli-0.7.0/tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
- tan_cli-0.7.0/tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
- tan_cli-0.7.0/tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
- tan_cli-0.7.0/tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
- tan_cli-0.7.0/tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
- tan_cli-0.7.0/tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
- tan_cli-0.7.0/tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
- tan_cli-0.7.0/tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
- tan_cli-0.7.0/tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
- tan_cli-0.7.0/tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
- tan_cli-0.7.0/tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
- tan_cli-0.7.0/tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
- tan_cli-0.7.0/tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
- tan_cli-0.7.0/tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
- tan_cli-0.7.0/tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
- tan_cli-0.7.0/tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
- tan_cli-0.7.0/tan/version.py +102 -0
- tan_cli-0.7.0/tan_cli.egg-info/PKG-INFO +125 -0
- tan_cli-0.7.0/tan_cli.egg-info/SOURCES.txt +352 -0
- tan_cli-0.7.0/tan_cli.egg-info/dependency_links.txt +1 -0
- tan_cli-0.7.0/tan_cli.egg-info/entry_points.txt +2 -0
- tan_cli-0.7.0/tan_cli.egg-info/requires.txt +26 -0
- tan_cli-0.7.0/tan_cli.egg-info/top_level.txt +1 -0
- tan_cli-0.7.0/tests/test_apt_bounded_archive_fallback.py +649 -0
- tan_cli-0.7.0/tests/test_apt_bounded_archive_fallback_formats.py +226 -0
- tan_cli-0.7.0/tests/test_apt_bounded_wrapper.py +631 -0
- tan_cli-0.7.0/tests/test_cli_lazy_commands.py +182 -0
- tan_cli-0.7.0/tests/test_cli_skeleton.py +458 -0
- tan_cli-0.7.0/tests/test_docker_pull_mirror.py +158 -0
- tan_cli-0.7.0/tests/test_e2e_linux_freeze_script.py +142 -0
- tan_cli-0.7.0/tests/test_env.py +211 -0
- tan_cli-0.7.0/tests/test_envelope.py +293 -0
- tan_cli-0.7.0/tests/test_envelope_last_resort.py +426 -0
- tan_cli-0.7.0/tests/test_envelope_surrogate.py +189 -0
- tan_cli-0.7.0/tests/test_interrupt_envelope.py +153 -0
- tan_cli-0.7.0/tests/test_main.py +127 -0
- tan_cli-0.7.0/tests/test_net.py +41 -0
- tan_cli-0.7.0/tests/test_probe_free_path.py +290 -0
- tan_cli-0.7.0/tests/test_stdout_bytes.py +125 -0
- tan_cli-0.7.0/tests/test_verify_binary_script.py +141 -0
- tan_cli-0.7.0/tests/test_wants_help_positions.py +184 -0
- tan_cli-0.7.0/tests/test_wants_json_positions.py +170 -0
tan_cli-0.7.0/PKG-INFO
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tan-cli
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: The Alp Lab build CLI: one board.yaml to firmware, across MCU and MPU cores.
|
|
5
|
+
Author: Alp Lab AB
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/alplabai/tan-cli
|
|
8
|
+
Project-URL: Repository, https://github.com/alplabai/tan-cli
|
|
9
|
+
Project-URL: Issues, https://github.com/alplabai/tan-cli/issues
|
|
10
|
+
Keywords: embedded,firmware,zephyr,yocto,som,build
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
17
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: typer<0.28,>=0.26
|
|
21
|
+
Requires-Dist: rich>=13
|
|
22
|
+
Requires-Dist: pyyaml>=6
|
|
23
|
+
Requires-Dist: jsonschema>=4.18
|
|
24
|
+
Requires-Dist: click>=8.1
|
|
25
|
+
Requires-Dist: truststore>=0.9
|
|
26
|
+
Requires-Dist: certifi>=2024.2.2
|
|
27
|
+
Requires-Dist: cbor2>=5.0
|
|
28
|
+
Provides-Extra: monitor
|
|
29
|
+
Requires-Dist: pyserial>=3.5; extra == "monitor"
|
|
30
|
+
Provides-Extra: model-io
|
|
31
|
+
Requires-Dist: tflite>=2.10; extra == "model-io"
|
|
32
|
+
Requires-Dist: flatbuffers>=23.1.21; extra == "model-io"
|
|
33
|
+
Provides-Extra: model-compile
|
|
34
|
+
Requires-Dist: ethos-u-vela>=3.9; extra == "model-compile"
|
|
35
|
+
Requires-Dist: tflite>=2.10; extra == "model-compile"
|
|
36
|
+
Requires-Dist: flatbuffers>=23.1.21; extra == "model-compile"
|
|
37
|
+
Provides-Extra: model
|
|
38
|
+
Requires-Dist: onnxruntime>=1.30.0; extra == "model"
|
|
39
|
+
Requires-Dist: onnx>=1.16.0; extra == "model"
|
|
40
|
+
Requires-Dist: numpy>=1.26; extra == "model"
|
|
41
|
+
Requires-Dist: sympy>=1.12; extra == "model"
|
|
42
|
+
|
|
43
|
+
# tan
|
|
44
|
+
|
|
45
|
+
The Alp Lab build CLI. One `board.yaml` describes a whole module — every core,
|
|
46
|
+
MCU and MPU alike — and `tan` turns it into firmware.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
Install from a checkout. `tan` is **not on PyPI yet** — the distribution has
|
|
51
|
+
never been published, so a `pip install` of it answers `ERROR: No matching
|
|
52
|
+
distribution found`. The decision to publish there HAS now been taken
|
|
53
|
+
(tan-cli#1054: PyPI is the sole standalone install channel, and the npm shim is
|
|
54
|
+
retired), and `.github/workflows/release.yml` carries a `publish_pypi` job — but
|
|
55
|
+
it is opt-in and off, waiting on the repository variable `TAN_PYPI_PUBLISH` and
|
|
56
|
+
a pending publisher configured on pypi.org. Until the first final tag uploads
|
|
57
|
+
under that arrangement, a checkout install is the only path.
|
|
58
|
+
|
|
59
|
+
On a PEP 668 host (Debian/Ubuntu, including stock `ubuntu:24.04`), installing
|
|
60
|
+
straight into the system interpreter refuses with
|
|
61
|
+
`error: externally-managed-environment`; install into a virtual environment
|
|
62
|
+
instead. Debian/Ubuntu's `python3` package does not include `venv` either --
|
|
63
|
+
`sudo apt-get install -y python3-venv` first, on those hosts:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/alplabai/tan-cli && cd tan-cli
|
|
67
|
+
python3 -m venv .venv && source .venv/bin/activate # Linux / macOS
|
|
68
|
+
py -m venv .venv && .venv\Scripts\Activate.ps1 # Windows
|
|
69
|
+
python3 -m pip install ./python # Linux / macOS
|
|
70
|
+
py -m pip install ./python # Windows
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
tan --version
|
|
75
|
+
tan init # scaffold a project (add --preview to see the plan first)
|
|
76
|
+
tan doctor # is this host able to build and flash?
|
|
77
|
+
tan build # build every core the module declares
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Add the `monitor` extra — `pip install "./python[monitor]"` — if you want `tan
|
|
81
|
+
monitor`; it needs pyserial, which a default install leaves out.
|
|
82
|
+
|
|
83
|
+
**Use `-m pip`, not a bare `pip` or `pip3`.** `tan` requires Python **3.12 or
|
|
84
|
+
newer**, and on a machine with several interpreters a bare `pip` can install into
|
|
85
|
+
a different one than the `python3` you will actually build with — on Windows,
|
|
86
|
+
`python3` is frequently the Microsoft Store stub rather than a real interpreter.
|
|
87
|
+
`python3 -m pip` cannot drift from the interpreter that runs it, so the
|
|
88
|
+
`requires-python = ">=3.12"` check either passes honestly or refuses that
|
|
89
|
+
interpreter, instead of installing somewhere you will not find it. (An old
|
|
90
|
+
`setuptools` on the wrong interpreter can fail earlier and for a different
|
|
91
|
+
reason — `project.license` must be valid exactly by one definition, measured
|
|
92
|
+
here on setuptools 68-era metadata handling; that is tan-cli#382, not a version
|
|
93
|
+
mismatch.)
|
|
94
|
+
|
|
95
|
+
**Not building from source?** Every version tag publishes a prebuilt archive per
|
|
96
|
+
platform, and `install.sh` / `install.ps1` fetch and verify one for you — see
|
|
97
|
+
the root [`README.md`](../README.md#install). That is the supported path for
|
|
98
|
+
someone who just wants the command.
|
|
99
|
+
|
|
100
|
+
`tan init` needs no alp-sdk checkout: the scaffold templates ship inside `tan`.
|
|
101
|
+
Most planning/build commands do need a checkout for metadata, schemas, examples,
|
|
102
|
+
and tooling; pass it with `--sdk-root` or place it beside the project.
|
|
103
|
+
|
|
104
|
+
The distribution is not named `tan` because that name is already taken on PyPI
|
|
105
|
+
(an unrelated code formatter); the command you run is still `tan`. Reserving a
|
|
106
|
+
name is not the same as occupying it — nothing is uploaded yet under any of
|
|
107
|
+
them.
|
|
108
|
+
|
|
109
|
+
## What it does
|
|
110
|
+
|
|
111
|
+
`tan` contains both the relocated planner and the executor. It produces a build
|
|
112
|
+
plan from your `board.yaml` plus alp-sdk metadata, then drives the right tool for
|
|
113
|
+
each core — `west` for Zephyr on Cortex-M, `bitbake` for Yocto on Cortex-A,
|
|
114
|
+
`cmake` for bare metal. You never pick an OS or a toolchain; both are derived
|
|
115
|
+
from each core's architecture.
|
|
116
|
+
|
|
117
|
+
## Requirements
|
|
118
|
+
|
|
119
|
+
Python 3.12 or newer — the floor Zephyr's own CMake configure enforces. Run
|
|
120
|
+
`tan doctor` to check the rest of the host; it names what is missing and where
|
|
121
|
+
to get it.
|
|
122
|
+
|
|
123
|
+
## Licence
|
|
124
|
+
|
|
125
|
+
Apache-2.0.
|
tan_cli-0.7.0/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# tan
|
|
2
|
+
|
|
3
|
+
The Alp Lab build CLI. One `board.yaml` describes a whole module — every core,
|
|
4
|
+
MCU and MPU alike — and `tan` turns it into firmware.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
Install from a checkout. `tan` is **not on PyPI yet** — the distribution has
|
|
9
|
+
never been published, so a `pip install` of it answers `ERROR: No matching
|
|
10
|
+
distribution found`. The decision to publish there HAS now been taken
|
|
11
|
+
(tan-cli#1054: PyPI is the sole standalone install channel, and the npm shim is
|
|
12
|
+
retired), and `.github/workflows/release.yml` carries a `publish_pypi` job — but
|
|
13
|
+
it is opt-in and off, waiting on the repository variable `TAN_PYPI_PUBLISH` and
|
|
14
|
+
a pending publisher configured on pypi.org. Until the first final tag uploads
|
|
15
|
+
under that arrangement, a checkout install is the only path.
|
|
16
|
+
|
|
17
|
+
On a PEP 668 host (Debian/Ubuntu, including stock `ubuntu:24.04`), installing
|
|
18
|
+
straight into the system interpreter refuses with
|
|
19
|
+
`error: externally-managed-environment`; install into a virtual environment
|
|
20
|
+
instead. Debian/Ubuntu's `python3` package does not include `venv` either --
|
|
21
|
+
`sudo apt-get install -y python3-venv` first, on those hosts:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
git clone https://github.com/alplabai/tan-cli && cd tan-cli
|
|
25
|
+
python3 -m venv .venv && source .venv/bin/activate # Linux / macOS
|
|
26
|
+
py -m venv .venv && .venv\Scripts\Activate.ps1 # Windows
|
|
27
|
+
python3 -m pip install ./python # Linux / macOS
|
|
28
|
+
py -m pip install ./python # Windows
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
tan --version
|
|
33
|
+
tan init # scaffold a project (add --preview to see the plan first)
|
|
34
|
+
tan doctor # is this host able to build and flash?
|
|
35
|
+
tan build # build every core the module declares
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Add the `monitor` extra — `pip install "./python[monitor]"` — if you want `tan
|
|
39
|
+
monitor`; it needs pyserial, which a default install leaves out.
|
|
40
|
+
|
|
41
|
+
**Use `-m pip`, not a bare `pip` or `pip3`.** `tan` requires Python **3.12 or
|
|
42
|
+
newer**, and on a machine with several interpreters a bare `pip` can install into
|
|
43
|
+
a different one than the `python3` you will actually build with — on Windows,
|
|
44
|
+
`python3` is frequently the Microsoft Store stub rather than a real interpreter.
|
|
45
|
+
`python3 -m pip` cannot drift from the interpreter that runs it, so the
|
|
46
|
+
`requires-python = ">=3.12"` check either passes honestly or refuses that
|
|
47
|
+
interpreter, instead of installing somewhere you will not find it. (An old
|
|
48
|
+
`setuptools` on the wrong interpreter can fail earlier and for a different
|
|
49
|
+
reason — `project.license` must be valid exactly by one definition, measured
|
|
50
|
+
here on setuptools 68-era metadata handling; that is tan-cli#382, not a version
|
|
51
|
+
mismatch.)
|
|
52
|
+
|
|
53
|
+
**Not building from source?** Every version tag publishes a prebuilt archive per
|
|
54
|
+
platform, and `install.sh` / `install.ps1` fetch and verify one for you — see
|
|
55
|
+
the root [`README.md`](../README.md#install). That is the supported path for
|
|
56
|
+
someone who just wants the command.
|
|
57
|
+
|
|
58
|
+
`tan init` needs no alp-sdk checkout: the scaffold templates ship inside `tan`.
|
|
59
|
+
Most planning/build commands do need a checkout for metadata, schemas, examples,
|
|
60
|
+
and tooling; pass it with `--sdk-root` or place it beside the project.
|
|
61
|
+
|
|
62
|
+
The distribution is not named `tan` because that name is already taken on PyPI
|
|
63
|
+
(an unrelated code formatter); the command you run is still `tan`. Reserving a
|
|
64
|
+
name is not the same as occupying it — nothing is uploaded yet under any of
|
|
65
|
+
them.
|
|
66
|
+
|
|
67
|
+
## What it does
|
|
68
|
+
|
|
69
|
+
`tan` contains both the relocated planner and the executor. It produces a build
|
|
70
|
+
plan from your `board.yaml` plus alp-sdk metadata, then drives the right tool for
|
|
71
|
+
each core — `west` for Zephyr on Cortex-M, `bitbake` for Yocto on Cortex-A,
|
|
72
|
+
`cmake` for bare metal. You never pick an OS or a toolchain; both are derived
|
|
73
|
+
from each core's architecture.
|
|
74
|
+
|
|
75
|
+
## Requirements
|
|
76
|
+
|
|
77
|
+
Python 3.12 or newer — the floor Zephyr's own CMake configure enforces. Run
|
|
78
|
+
`tan doctor` to check the rest of the host; it names what is missing and where
|
|
79
|
+
to get it.
|
|
80
|
+
|
|
81
|
+
## Licence
|
|
82
|
+
|
|
83
|
+
Apache-2.0.
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
# DISTRIBUTION name is `tan-cli`, not `tan`: the name `tan` is already taken on
|
|
3
|
+
# PyPI ("The compromising code formatter", v23.7.0), so `pip install tan` would
|
|
4
|
+
# fetch an unrelated package. The IMPORT package stays `tan` and the console
|
|
5
|
+
# script stays `tan`, so a customer still types `tan build` -- only the install
|
|
6
|
+
# line differs:
|
|
7
|
+
#
|
|
8
|
+
# pip install tan-cli -> tan --version
|
|
9
|
+
#
|
|
10
|
+
# `alp-tan` was the first choice and was also free, preferred at the time
|
|
11
|
+
# because the other Alp Lab artefacts are `alp-`prefixed. Reversed in
|
|
12
|
+
# tan-cli#1054, before anything was ever uploaded under either name: this
|
|
13
|
+
# distribution is published from `github.com/alplabai/tan-cli` and
|
|
14
|
+
# authenticated by Trusted Publishing, which binds the PyPI project to that
|
|
15
|
+
# exact repository. `pypi.org/project/tan-cli` <-> `github.com/alplabai/tan-cli`
|
|
16
|
+
# is one name a reader can follow; `alp-tan` <-> `tan-cli` is two.
|
|
17
|
+
name = "tan-cli"
|
|
18
|
+
# The PEP 440 RENDERING of `python/tan/version.py`'s TAN_VERSION, which is the
|
|
19
|
+
# source of truth -- never edited alone. `0.5.0-rc5.dev0` -> `0.5.0rc5.dev0`
|
|
20
|
+
# (the two spellings are not interchangeable and cannot be string-compared);
|
|
21
|
+
# `python/scripts/version_check.py` owns that mapping and CI runs it.
|
|
22
|
+
version = "0.7.0"
|
|
23
|
+
description = "The Alp Lab build CLI: one board.yaml to firmware, across MCU and MPU cores."
|
|
24
|
+
readme = "README.md"
|
|
25
|
+
# PEP 639 SPDX-expression form, NOT the `{ text = ... }` table -- and it is
|
|
26
|
+
# COUPLED to the `setuptools>=77.0.1` floor in `[build-system]` below, which
|
|
27
|
+
# exists solely to parse this line (tan-cli#382). Changing either without the
|
|
28
|
+
# other is the bug: the table form is what setuptools <77 accepts and what
|
|
29
|
+
# setuptools >=77 deprecates, so there is no spelling that satisfies both.
|
|
30
|
+
license = "Apache-2.0"
|
|
31
|
+
authors = [{ name = "Alp Lab AB" }]
|
|
32
|
+
keywords = ["embedded", "firmware", "zephyr", "yocto", "som", "build"]
|
|
33
|
+
# 3.12, not 3.11: the floor is what the BUILD needs, not what this package's own
|
|
34
|
+
# syntax needs. Zephyr's `cmake/modules/python.cmake` sets
|
|
35
|
+
# `PYTHON_MINIMUM_REQUIRED 3.12`, and `tan build` bakes the interpreter it
|
|
36
|
+
# resolves into every Zephyr slice as `-DPython3_EXECUTABLE` -- so a `tan` frozen
|
|
37
|
+
# against 3.11 hands Zephyr an interpreter its own configure step then rejects,
|
|
38
|
+
# and the customer gets an error naming Zephyr rather than tan. Same effective
|
|
39
|
+
# floor `tan doctor` enforces (`ZEPHYR_PYTHON_FLOOR`, tan/commands/doctor_cmd.py).
|
|
40
|
+
requires-python = ">=3.12"
|
|
41
|
+
# `pyyaml` is load-bearing, not optional: `build/system-manifest.yaml` is the ONLY
|
|
42
|
+
# input `tan image` and `tan size` have, and it is YAML. Without it declared here
|
|
43
|
+
# the PyInstaller build -- which installs exactly this set into a clean venv, see
|
|
44
|
+
# scripts/build_binary.sh -- ships a frozen `tan` whose `size`/`image` can never
|
|
45
|
+
# read a manifest. Both still degrade to a coded `*.manifest-invalid` envelope
|
|
46
|
+
# rather than a traceback if it is somehow absent (`tan.core.system_manifest.
|
|
47
|
+
# load_yaml_document`), but that is a safety net, not the plan. It is now doubly
|
|
48
|
+
# load-bearing: the relocated planner (`tan.planner`) reads every `metadata/**`
|
|
49
|
+
# YAML through it.
|
|
50
|
+
#
|
|
51
|
+
# `jsonschema` arrived WITH the planner and is the fourth runtime dependency.
|
|
52
|
+
# The four-name ceiling (typer, rich, pytest, pyinstaller) was scoped to the
|
|
53
|
+
# executor stage; the planner validates every `board.yaml` against
|
|
54
|
+
# `metadata/schemas/board.schema.json` under Draft 2020-12 before it plans
|
|
55
|
+
# anything (`tan/planner/loader.py`), and that schema is where invariants I-02
|
|
56
|
+
# (no top-level `os:`) and I-03 (`core_entry` is closed) are actually ENFORCED.
|
|
57
|
+
# Reimplementing the validator to save a dependency would move two enforced
|
|
58
|
+
# invariants into hand-written code -- the opposite of consume-what-exists. The
|
|
59
|
+
# planner `sys.exit`s with an install hint if it is absent and `tan build` turns
|
|
60
|
+
# that into a coded `build.plan-unavailable` envelope, never a traceback.
|
|
61
|
+
#
|
|
62
|
+
# `click` is the fifth, and it is declared because `tan/cli.py` and
|
|
63
|
+
# `tan/commands/new_som_cmd.py` IMPORT IT DIRECTLY -- `from click.testing
|
|
64
|
+
# import CliRunner` is how `--help` under `--format json` is captured into one
|
|
65
|
+
# envelope instead of Click printing straight to stdout and calling
|
|
66
|
+
# `ctx.exit(0)` before `emit()` can run (`_emit_help_envelope`). It rode along
|
|
67
|
+
# transitively for years because typer depended on click; starting at **typer
|
|
68
|
+
# 0.26.0** it does not (measured: PyPI `requires_dist` for 0.25.1 still lists
|
|
69
|
+
# `click>=8.2.1`; 0.26.0's does not, and its wheel gained a vendored
|
|
70
|
+
# `typer/_click/` package -- typer's OWN click-alike, not a re-export of the
|
|
71
|
+
# public one). So a clean `pip install tan-cli` produced `ModuleNotFoundError:
|
|
72
|
+
# No module named 'click'` on first run, and every local test suite passed
|
|
73
|
+
# regardless because click was present for other reasons. A package that
|
|
74
|
+
# imports a module must declare it; leaning on someone else's dependency graph
|
|
75
|
+
# is what broke. `tests/gates/test_declared_dependencies.py` fails if any
|
|
76
|
+
# module under `tan/` imports a distribution this list omits. `click` stays
|
|
77
|
+
# required even though typer no longer needs it transitively -- the two direct
|
|
78
|
+
# imports above are real, not incidental.
|
|
79
|
+
#
|
|
80
|
+
# `typer` is capped, not just floored, and the cap is the point of this whole
|
|
81
|
+
# comment. `tan/cli.py:_emit_help_envelope` drives `typer.main.get_command()`'s
|
|
82
|
+
# result -- a `TyperCommand` -- through the PUBLIC `click.testing.CliRunner`.
|
|
83
|
+
# Measured: `type(get_command(app)).__mro__` descends from
|
|
84
|
+
# `typer._click.core.Command`, typer's PRIVATE vendored fork, not from
|
|
85
|
+
# `click.core.Command` -- so this mixes two different click implementations,
|
|
86
|
+
# and it only works because the vendored `UsageError.show()` still exits via
|
|
87
|
+
# `SystemExit`, which `CliRunner` catches generically (see the docstring at
|
|
88
|
+
# that function). That is stable across the vendored-click line -- measured
|
|
89
|
+
# clean (`tests/test_cli_skeleton.py` + `tests/gates`) against both 0.26.0 and
|
|
90
|
+
# 0.27.0, the only two releases of that line so far -- but it is an
|
|
91
|
+
# implementation detail of typer's private fork, not a contract, so nothing
|
|
92
|
+
# says a `0.28` won't change what `get_command()` returns or how its group
|
|
93
|
+
# raises. Uncapped, a resolver is free to pick that day's newest typer for a
|
|
94
|
+
# `pip install ./python` -- the documented path on win32/arm64 and
|
|
95
|
+
# linux/arm64, the two hosts with no frozen asset -- and hand it a CLI built
|
|
96
|
+
# against a private surface that may no longer exist.
|
|
97
|
+
#
|
|
98
|
+
# The floor moved off `>=0.12` for the same "measure, don't guess" reason:
|
|
99
|
+
# `typer==0.12.0` on PyPI is a metapackage stub (`requires_dist` is just
|
|
100
|
+
# `typer-slim[standard]==0.12.0` + `typer-cli==0.12.0`), not a working install
|
|
101
|
+
# of the real `typer` module -- a floor including it was already claiming
|
|
102
|
+
# support that cannot work. `>=0.26` also makes the floor mean something: every
|
|
103
|
+
# version it admits is the SAME vendored-click behaviour this whole comment
|
|
104
|
+
# and `tests/gates/test_typer_click_bound.py` are about, not two different
|
|
105
|
+
# regimes (pre- and post-vendoring) blurred into one range. Pre-0.26 typer may
|
|
106
|
+
# well still work -- it was not the question this port needed answered.
|
|
107
|
+
#
|
|
108
|
+
# `tests/gates/test_typer_click_bound.py` fails if the installed typer drifts
|
|
109
|
+
# outside `<0.28`, or if the private surface this range assumes (`TyperCommand`
|
|
110
|
+
# descending from `typer._click.core.Command`, and a `SystemExit`-raising
|
|
111
|
+
# `UsageError`) goes away from an in-range version -- so a version bump here is
|
|
112
|
+
# a decision, not a drift.
|
|
113
|
+
#
|
|
114
|
+
# `truststore` and `certifi` are REQUIRED, not optional, and both are new
|
|
115
|
+
# (tan-cli#304): a PyInstaller `--onefile` freeze bundles its own `ssl` but no
|
|
116
|
+
# CA bundle and does not fall back to the platform trust store, so a frozen
|
|
117
|
+
# `tan` had ZERO trust anchors -- every `urllib` HTTPS call in `sdk_cmd.py`
|
|
118
|
+
# failed `CERTIFICATE_VERIFY_FAILED` even on a host where `curl`/a browser/
|
|
119
|
+
# system `python3` verified the same endpoint fine. `truststore` is preferred
|
|
120
|
+
# (verifies through the OS's own trust store, so a real corporate CA installed
|
|
121
|
+
# in the machine's keychain still works -- `certifi`'s fixed public-CA list
|
|
122
|
+
# alone would trade that failure for a different one); `certifi`'s bundled
|
|
123
|
+
# `cacert.pem` is the floor `tan/net.py` falls back to if `truststore` is
|
|
124
|
+
# absent or its platform verifier fails. Both are module-scope imports inside
|
|
125
|
+
# `tan/net.py`, so both must be REQUIRED per the reasoning right below this
|
|
126
|
+
# comment.
|
|
127
|
+
# `cbor2` arrived WITH the relocated model engine (ADR-0028 Task 2,
|
|
128
|
+
# `tan/model/manifest.py`): `.alpmodel` is a CBOR container, and
|
|
129
|
+
# `Manifest.to_cbor`/`from_cbor` do a module-scope `import cbor2 as _cbor2` --
|
|
130
|
+
# not lazy, not guarded -- so it is REQUIRED, matching alp-sdk's own
|
|
131
|
+
# classification of the same distribution in its own `pyproject.toml`
|
|
132
|
+
# (`cbor2>=5.0`, alongside PyYAML/jsonschema/click, never under
|
|
133
|
+
# `optional-dependencies`). `tests/gates/test_declared_dependencies.py` fails on
|
|
134
|
+
# this exact shape otherwise -- an eager import of an undeclared distribution.
|
|
135
|
+
dependencies = [
|
|
136
|
+
"typer>=0.26,<0.28",
|
|
137
|
+
"rich>=13",
|
|
138
|
+
"pyyaml>=6",
|
|
139
|
+
"jsonschema>=4.18",
|
|
140
|
+
"click>=8.1",
|
|
141
|
+
"truststore>=0.9",
|
|
142
|
+
"certifi>=2024.2.2",
|
|
143
|
+
"cbor2>=5.0",
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
# `pyserial` is OPTIONAL, not required: only `tan monitor` needs it, and a serial
|
|
147
|
+
# console is not worth adding to every install of a build tool. `monitor_cmd`
|
|
148
|
+
# imports it lazily inside the two functions that use it and refuses with an
|
|
149
|
+
# actionable message when it is absent, so `tan --version` and every other
|
|
150
|
+
# command still work without it. That laziness is load-bearing, not incidental --
|
|
151
|
+
# a module-scope `import serial` would make the whole CLI unimportable on a clean
|
|
152
|
+
# install the moment `monitor` is registered, so
|
|
153
|
+
# `tests/gates/test_declared_dependencies.py` fails if an extras-only import
|
|
154
|
+
# migrates to module scope.
|
|
155
|
+
classifiers = [
|
|
156
|
+
"Development Status :: 3 - Alpha",
|
|
157
|
+
"Environment :: Console",
|
|
158
|
+
"Intended Audience :: Developers",
|
|
159
|
+
"Programming Language :: Python :: 3.12",
|
|
160
|
+
"Programming Language :: Python :: 3.13",
|
|
161
|
+
"Topic :: Software Development :: Build Tools",
|
|
162
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# LAST of the `[project]` content on purpose. A table header ends the parent
|
|
167
|
+
# table, so every bare `key = ...` after it belongs to the SUB-table, not to
|
|
168
|
+
# `[project]`. Putting this above `classifiers` silently reparented it into the
|
|
169
|
+
# extras group -- `optional-dependencies` grew a bogus `classifiers` extra,
|
|
170
|
+
# `project.classifiers` vanished from the metadata, and `pip install ./python`
|
|
171
|
+
# failed outright. Keep new `[project]` KEYS above this line and new
|
|
172
|
+
# `[project.*]` TABLES below it.
|
|
173
|
+
[project.optional-dependencies]
|
|
174
|
+
monitor = ["pyserial>=3.5"]
|
|
175
|
+
# Lightweight TFLite read/parse deps for `tan.model.tensorio.extract_io` (tensor
|
|
176
|
+
# I/O extraction from a .tflite flatbuffer) -- pure-Python, no compiler toolchain.
|
|
177
|
+
# `tflite` is a genuinely LAZY, guarded import (`tensorio.py`'s
|
|
178
|
+
# `try: import tflite / except ImportError: return [], []`), so it stays an
|
|
179
|
+
# extra rather than a required dependency -- `extract_io` degrades to "no I/O
|
|
180
|
+
# metadata" rather than failing when it is absent. `flatbuffers` is not imported
|
|
181
|
+
# by name anywhere under `tan/` (so `test_declared_dependencies.py` does not
|
|
182
|
+
# require it), but it is `tflite`'s own declared runtime dependency (measured:
|
|
183
|
+
# `pip show tflite` -> `Requires: flatbuffers, numpy`) -- pinned explicitly here,
|
|
184
|
+
# not left to a transitive resolve, so `pip install tan-cli[model-io]` is a
|
|
185
|
+
# working reader and not just a gate-satisfying stub. Pins mirror alp-sdk's own
|
|
186
|
+
# `model-io` extra exactly (its `pyproject.toml`'s entry, due to be dropped once
|
|
187
|
+
# `scripts/alp_model/` is deleted -- ADR-0028 Task 6) rather than inventing new
|
|
188
|
+
# floors.
|
|
189
|
+
model-io = ["tflite>=2.10", "flatbuffers>=23.1.21"]
|
|
190
|
+
|
|
191
|
+
# Full host model-compile toolchain: adds the Arm Vela compiler `vela` needs
|
|
192
|
+
# for `tan.model.adapters.ethos_u.VelaAdapter.compile()` (`shutil.which("vela")`
|
|
193
|
+
# is the runtime gate; this extra is what puts it on PATH). NOT required for
|
|
194
|
+
# `tan model build` to run -- every vendor adapter shells its compiler as an
|
|
195
|
+
# external binary, never a Python import, so a bare `pip install tan-cli`
|
|
196
|
+
# still works and only that one NPU backend's compile step is unavailable
|
|
197
|
+
# (`is_available()` reports False; `doctor`/`check` explain why). Pins mirror
|
|
198
|
+
# alp-sdk's own `model-compile` extra exactly (due to be dropped from
|
|
199
|
+
# alp-sdk's `pyproject.toml` once `scripts/alp_model/` is deleted -- ADR-0028
|
|
200
|
+
# Task 6) rather than inventing new floors. DEEPX's `dxcom` and DRP-AI's TVM
|
|
201
|
+
# have no such extra on either side -- both are license-gated vendor
|
|
202
|
+
# toolchains with no PyPI wheel to pin.
|
|
203
|
+
model-compile = ["ethos-u-vela>=3.9", "tflite>=2.10", "flatbuffers>=23.1.21"]
|
|
204
|
+
|
|
205
|
+
# Host-tier `tan model prep` / `run` / `ab` (tan-cli#1287): onnxruntime-based INT8
|
|
206
|
+
# quantization and host reference runs. OPTIONAL and deliberately NOT in the
|
|
207
|
+
# frozen release assets (`.[monitor]` only) -- onnxruntime publishes no macOS
|
|
208
|
+
# x86_64 wheel and adds tens of MB for a verb most users never run. Every import
|
|
209
|
+
# is lazy (`tan.model.prep`, `tan.model.measure`); `tan.core.model_host` turns a
|
|
210
|
+
# missing one into the coded `model.model-extra-missing` refusal. `sympy` is
|
|
211
|
+
# onnxruntime's symbolic shape-inference dependency for `quant_pre_process`.
|
|
212
|
+
# Floors: `onnx>=1.16.0` is the release that fixed CVE-2024-27318 (path traversal
|
|
213
|
+
# through external-data locations) -- `tan model prep` loads customer-supplied
|
|
214
|
+
# models. `onnxruntime>=1.30.0` is the version the external-data and corrupt-
|
|
215
|
+
# sample refusal tests were actually run against; a lower floor was not tested.
|
|
216
|
+
model = ["onnxruntime>=1.30.0", "onnx>=1.16.0", "numpy>=1.26", "sympy>=1.12"]
|
|
217
|
+
|
|
218
|
+
[project.urls]
|
|
219
|
+
Homepage = "https://github.com/alplabai/tan-cli"
|
|
220
|
+
Repository = "https://github.com/alplabai/tan-cli"
|
|
221
|
+
Issues = "https://github.com/alplabai/tan-cli/issues"
|
|
222
|
+
|
|
223
|
+
[project.scripts]
|
|
224
|
+
tan = "tan.__main__:main"
|
|
225
|
+
|
|
226
|
+
[build-system]
|
|
227
|
+
# 77.0.1, measured, not guessed (tan-cli#382). `>=68` was a bound nobody had
|
|
228
|
+
# ever built at: PEP 639 support -- the `license = "Apache-2.0"` string form at
|
|
229
|
+
# the top of this file -- landed in the setuptools 77 line, so every version
|
|
230
|
+
# `>=68` admitted below that REJECTS this file's own metadata:
|
|
231
|
+
#
|
|
232
|
+
# pip install setuptools==76.1.0
|
|
233
|
+
# pip wheel --no-deps --no-build-isolation ./python
|
|
234
|
+
# configuration error: `project.license` must be valid exactly by one
|
|
235
|
+
# definition (2 matches found) <- expected `file` or `text`
|
|
236
|
+
#
|
|
237
|
+
# Measured on py3.12.10: 68.1.2 FAIL, 76.1.0 FAIL (the release immediately
|
|
238
|
+
# below the floor), 77.0.1 PASS. Build ISOLATION is what hid it for two years
|
|
239
|
+
# -- pip then downloads the newest setuptools regardless of this line, so every
|
|
240
|
+
# other install path in this repo (`pip install -e ./python` in ci.yml, the
|
|
241
|
+
# venvs in release.yml / python-binaries.yml / clean-host.yml) passes no matter
|
|
242
|
+
# what number is written here. Only a downstream packager pinned to a valid
|
|
243
|
+
# `>=68` environment ever saw it, and they cannot build the distribution at all.
|
|
244
|
+
#
|
|
245
|
+
# 77.0.1 and not `>=77`: setuptools 77.0.0 and 77.0.2 have NO files on PyPI, so
|
|
246
|
+
# `==77` (which PEP 440 zero-pads to 77.0.0) resolves to nothing. 77.0.1 is the
|
|
247
|
+
# first release of that line a resolver can actually install, which makes this
|
|
248
|
+
# bound an exact version the `wheel-floor` job in .github/workflows/ci.yml can
|
|
249
|
+
# pin and build at -- that job derives its pin FROM this string, so a future
|
|
250
|
+
# change here is tested at its own floor with nothing else to update.
|
|
251
|
+
requires = ["setuptools>=77.0.1"]
|
|
252
|
+
build-backend = "setuptools.build_meta"
|
|
253
|
+
|
|
254
|
+
# Explicit, so `tests/` can never be shipped by an auto-discovery change and a
|
|
255
|
+
# new subpackage is never silently omitted from the wheel.
|
|
256
|
+
[tool.setuptools.packages.find]
|
|
257
|
+
where = ["."]
|
|
258
|
+
include = ["tan*"]
|
|
259
|
+
|
|
260
|
+
# `tan init`'s scaffold trees are DATA, not code, so nothing in the import graph
|
|
261
|
+
# pulls them into a wheel on its own -- and without them five of the six
|
|
262
|
+
# templates fail (`init.template-unreadable`). The frozen binary needs the
|
|
263
|
+
# equivalent `--add-data`, see `scripts/build_binary.sh`.
|
|
264
|
+
[tool.setuptools.package-data]
|
|
265
|
+
"tan.templates" = ["vendored/**/*"]
|
|
266
|
+
|
|
267
|
+
[tool.pytest.ini_options]
|
|
268
|
+
pythonpath = ["."]
|
|
269
|
+
|
|
270
|
+
# tan-cli#408. ADVISORY ONLY -- read this before believing it enforces
|
|
271
|
+
# anything. No workflow under `.github/workflows/` runs ruff, so this section
|
|
272
|
+
# changes exactly one thing: an editor with ruff wired in now surfaces the
|
|
273
|
+
# three complexity rules while you type, instead of nothing at all.
|
|
274
|
+
#
|
|
275
|
+
# What actually ENFORCES the size guideline is
|
|
276
|
+
# `tests/gates/test_module_size_budget.py`, a ratchet that runs inside
|
|
277
|
+
# `python -- pytest across python/` -- already a required context on `main`
|
|
278
|
+
# and `dev`, so it gates merges today with no branch-protection change. A new
|
|
279
|
+
# required CI job would have to be added to that list to matter, and adding a
|
|
280
|
+
# required context blocks every open PR until it has run on each of them.
|
|
281
|
+
#
|
|
282
|
+
# `ruff check python/` is red on this tree, deliberately and honestly: 23
|
|
283
|
+
# modules are over 800 lines and 199 functions over 50. The gate records that
|
|
284
|
+
# as a baseline and fails on GROWTH; ruff here reports the standing debt.
|
|
285
|
+
[tool.ruff]
|
|
286
|
+
line-length = 100
|
|
287
|
+
target-version = "py312"
|
|
288
|
+
|
|
289
|
+
[tool.ruff.lint]
|
|
290
|
+
# PLR0911 too-many-return-statements, PLR0912 too-many-branches,
|
|
291
|
+
# PLR0915 too-many-statements -- the three `bootstrap_cmd._run`'s lone
|
|
292
|
+
# `# noqa` names, and the only `noqa: PLR` anywhere under `tan/`.
|
|
293
|
+
extend-select = ["PLR0911", "PLR0912", "PLR0915"]
|
tan_cli-0.7.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""The `tan` entrypoint. The command surface itself lives in `tan.cli`.
|
|
3
|
+
|
|
4
|
+
This is the PROCESS BOUNDARY -- the one place both real entrypoints resolve
|
|
5
|
+
to: `pyproject.toml`'s `[project.scripts]` names `tan.__main__:main` (so a
|
|
6
|
+
`pip install`ed console-script calls this module's `main` attribute directly,
|
|
7
|
+
never running the `if __name__ == "__main__"` guard below), and
|
|
8
|
+
`scripts/build_binary.sh` points PyInstaller at this same file. A broken-pipe
|
|
9
|
+
guard sprinkled into each command instead would miss both.
|
|
10
|
+
"""
|
|
11
|
+
import errno
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from tan.cli import main as _cli_main
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _is_broken_pipe(exc: BaseException) -> bool:
|
|
19
|
+
"""Is this the OSError a reader closing tan's stdout raises?
|
|
20
|
+
|
|
21
|
+
EPIPE on POSIX. On Windows the same condition surfaces as a plain
|
|
22
|
+
`OSError` with `errno.EINVAL` instead -- CPython's Windows layer maps the
|
|
23
|
+
underlying `ERROR_NO_DATA` to EINVAL, not EPIPE, so `except
|
|
24
|
+
BrokenPipeError` alone (the POSIX-only idiom from the `signal` docs' "Note
|
|
25
|
+
on SIGPIPE") does not catch it there.
|
|
26
|
+
|
|
27
|
+
EINVAL alone is not pipe-specific on Windows -- a plain filesystem error
|
|
28
|
+
(e.g. `open("bad<n.txt")`) raises the same errno. That OSError carries a
|
|
29
|
+
`filename`; the broken-pipe OSError does not (verified against CPython's
|
|
30
|
+
Windows console-write path), so `filename is None` discriminates a closed
|
|
31
|
+
stdout from a real filesystem error without weakening the pipe guard.
|
|
32
|
+
"""
|
|
33
|
+
return isinstance(exc, OSError) and (
|
|
34
|
+
exc.errno == errno.EPIPE
|
|
35
|
+
or (sys.platform == "win32" and exc.errno == errno.EINVAL and exc.filename is None)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _caused_by_broken_pipe(exc: BaseException) -> bool:
|
|
40
|
+
"""Was this `SystemExit` raised WHILE a closed stdout was being handled?
|
|
41
|
+
|
|
42
|
+
Nothing reaches this module's `except OSError` arm on POSIX, because two
|
|
43
|
+
layers below it already convert the `BrokenPipeError` into an exit of
|
|
44
|
+
their own and neither is reachable through a public hook:
|
|
45
|
+
|
|
46
|
+
* Rich -- `Console._check_buffer` catches `BrokenPipeError` and calls
|
|
47
|
+
`Console.on_broken_pipe`, which sets `quiet` and raises `SystemExit(1)`.
|
|
48
|
+
This is the one that fires for `tan --help`, since Typer renders every
|
|
49
|
+
`--help` through Rich.
|
|
50
|
+
* Typer/Click -- `typer.core._main` (Click's `BaseCommand.main` shape)
|
|
51
|
+
has `except OSError: if e.errno == errno.EPIPE: ... sys.exit(1)`.
|
|
52
|
+
|
|
53
|
+
Both raise their exit from INSIDE the `except BrokenPipeError`/`except
|
|
54
|
+
OSError` block that caught the pipe, so CPython's implicit chaining leaves
|
|
55
|
+
the original error on `SystemExit.__context__`. Following that chain is
|
|
56
|
+
what identifies the case: `SystemExit(1)` on its own is far too broad to
|
|
57
|
+
remap -- every failed `tan build` raises it -- and the alternatives are
|
|
58
|
+
all private detail of somebody else's library (Click's
|
|
59
|
+
`PacifyFlushWrapper` swap, Rich's `console.quiet`), which an entrypoint
|
|
60
|
+
should not be importing.
|
|
61
|
+
"""
|
|
62
|
+
seen: set[int] = set()
|
|
63
|
+
cause: BaseException | None = exc.__cause__ or exc.__context__
|
|
64
|
+
while cause is not None and id(cause) not in seen:
|
|
65
|
+
if _is_broken_pipe(cause):
|
|
66
|
+
return True
|
|
67
|
+
seen.add(id(cause))
|
|
68
|
+
cause = cause.__cause__ or cause.__context__
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _exit_quietly() -> None:
|
|
73
|
+
"""Exit 0 with a stdout that can no longer raise.
|
|
74
|
+
|
|
75
|
+
Redirecting stdout to the null device before returning matters: without
|
|
76
|
+
it, the interpreter's own shutdown-time flush of the now-broken stdout
|
|
77
|
+
raises the same OSError again, straight past any exception handler, and
|
|
78
|
+
prints "Exception ignored in: ..." on stderr regardless.
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
devnull = os.open(os.devnull, os.O_WRONLY)
|
|
82
|
+
os.dup2(devnull, sys.stdout.fileno())
|
|
83
|
+
except OSError:
|
|
84
|
+
pass
|
|
85
|
+
sys.exit(0)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main() -> None:
|
|
89
|
+
"""Run the real CLI, and turn a closed stdout into a quiet exit.
|
|
90
|
+
|
|
91
|
+
`tan generate --help | grep -q x` (or `| head`, any reader that stops
|
|
92
|
+
early) closes tan's stdout mid-write. The Rust oracle exits 0 with empty
|
|
93
|
+
stderr in that situation on BOTH hosts -- measured: `tan.exe generate
|
|
94
|
+
--help | head -n1` on Windows, and tan 0.4.1 linux-musl writing a
|
|
95
|
+
4327-byte `--help` into a never-read 4 KiB pipe, which is a guaranteed
|
|
96
|
+
EPIPE. This pins the port to the same observable behaviour, and it takes
|
|
97
|
+
two arms because the pipe reaches this frame in two different shapes:
|
|
98
|
+
|
|
99
|
+
* POSIX -- as a `SystemExit(1)` some library layer already raised for it
|
|
100
|
+
(see `_caused_by_broken_pipe`). Left alone, `tan --help | head -n1`
|
|
101
|
+
exited 1 here where the oracle exits 0.
|
|
102
|
+
* Windows -- as the raw `OSError`, because the errno is EINVAL and none of
|
|
103
|
+
those layers recognises it (see `_is_broken_pipe`). Left unhandled,
|
|
104
|
+
Typer's installed pretty-exception hook renders the whole stack as a
|
|
105
|
+
boxed traceback and the process exits non-zero.
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
_cli_main()
|
|
109
|
+
except SystemExit as exc:
|
|
110
|
+
if not _caused_by_broken_pipe(exc):
|
|
111
|
+
raise
|
|
112
|
+
_exit_quietly()
|
|
113
|
+
except OSError as exc:
|
|
114
|
+
if not _is_broken_pipe(exc):
|
|
115
|
+
raise
|
|
116
|
+
_exit_quietly()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
main()
|