diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml new file mode 100644 index 0000000..2c67f7b --- /dev/null +++ b/.github/workflows/labels.yml @@ -0,0 +1,34 @@ +# Copied from https://github.com/rerun-io/rerun_template + +# https://github.com/marketplace/actions/require-labels +# Check for existence of labels +# See all our labels at https://github.com/rerun-io/rerun/issues/labels + +name: PR Labels + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - labeled + - unlabeled + +jobs: + label: + runs-on: ubuntu-latest + steps: + - name: Check for a "do-not-merge" label + uses: mheap/github-action-required-labels@v3 + with: + mode: exactly + count: 0 + labels: "do-not-merge" + + - name: Require label "include in changelog" or "exclude from changelog" + uses: mheap/github-action-required-labels@v3 + with: + mode: minimum + count: 1 + labels: "exclude from changelog, include in changelog" diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml new file mode 100644 index 0000000..8bb984c --- /dev/null +++ b/.github/workflows/links.yml @@ -0,0 +1,29 @@ +# Copied from https://github.com/rerun-io/rerun_template +on: [push, pull_request] + +name: Link checker + +jobs: + link-checker: + name: Check links + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Restore link checker cache + uses: actions/cache@v3 + with: + path: .lycheecache + key: cache-lychee-${{ github.sha }} + restore-keys: cache-lychee- + + # Check https://github.com/lycheeverse/lychee on how to run locally. + - name: Link Checker + id: lychee + uses: lycheeverse/lychee-action@v1.9.0 + with: + fail: true + lycheeVersion: "0.14.3" + # When given a directory, lychee checks only markdown, html and text files, everything else we have to glob in manually. + args: | + --base . --cache --max-cache-age 1d . "**/*.rs" "**/*.toml" "**/*.hpp" "**/*.cpp" "**/CMakeLists.txt" "**/*.py" "**/*.yml" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..4b66ce1 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,109 @@ +# Copied from https://github.com/rerun-io/rerun_template +on: [push, pull_request] + +name: Rust + +env: + RUSTFLAGS: -D warnings + RUSTDOCFLAGS: -D warnings + +jobs: + rust-check: + name: Rust + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - uses: actions-rs/toolchain@v1 + with: + profile: default + toolchain: 1.76.0 + override: true + + - name: Install packages (Linux) + uses: awalsh128/cache-apt-pkgs-action@v1.3.0 + with: + # Random stuff required by `bevy` + packages: libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libasound2-dev libudev-dev + version: 1.0 + execute_install_scripts: true + + - name: Set up cargo cache + uses: Swatinem/rust-cache@v2 + + - name: Rustfmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - name: Install cargo-cranky + uses: baptiste0928/cargo-install@v1 + with: + crate: cargo-cranky + + - name: check --all-features + uses: actions-rs/cargo@v1 + with: + command: check + args: --all-features --all-targets + + - name: check default features + uses: actions-rs/cargo@v1 + with: + command: check + args: --all-targets + + - name: check --no-default-features + uses: actions-rs/cargo@v1 + with: + command: check + args: --no-default-features --lib --all-targets + + - name: Test doc-tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --doc --all-features + + - name: cargo doc --lib + uses: actions-rs/cargo@v1 + with: + command: doc + args: --lib --no-deps --all-features + + - name: cargo doc --document-private-items + uses: actions-rs/cargo@v1 + with: + command: doc + args: --document-private-items --no-deps --all-features + + - name: Build tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features --no-run + + - name: Run test + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features + + - name: Cranky + uses: actions-rs/cargo@v1 + with: + command: cranky + args: --all-targets --all-features -- -D warnings + + # --------------------------------------------------------------------------- + + cargo-deny: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: EmbarkStudios/cargo-deny-action@v1 + with: + rust-version: "1.76.0" + log-level: warn + command: check diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml new file mode 100644 index 0000000..3055f87 --- /dev/null +++ b/.github/workflows/typos.yml @@ -0,0 +1,19 @@ +# Copied from https://github.com/rerun-io/rerun_template + +# https://github.com/crate-ci/typos +# Add exceptions to `.typos.toml` +# install and run locally: cargo install typos-cli && typos + +name: Spell Check +on: [pull_request] + +jobs: + run: + name: Spell Check + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + + - name: Check spelling of entire workspace + uses: crate-ci/typos@master diff --git a/.gitignore b/.gitignore index 057c620..b99da30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,59 +1,12 @@ +# Mac stuff: .DS_Store -# Codegen stuff: -crates/re_types/source_hash.txt -crates/re_types_builder/source_hash.txt +# Rust compile target directories: +target +target_ra +target_wasm -# C++ and CMake stuff: -*.a -*.bin -*.o -**/arrow/ -**/build/ -**/build-msvc/ -**/CMakeFiles/ -**/CMakeCache.txt -**/Makefile -**/cmake_install.cmake -_deps -**/.cache/ -**/rerun_cpp/docs/html +# https://github.com/lycheeverse/lychee +.lycheecache -# Rust compile target directory: -**/target -**/target_ra -**/target_wasm - -# Python virtual environment: -**/venv* -.python-version - -# Python build artifacts: -__pycache__ -*.pyc -*.so - -# Pixi environment -.pixi - -.gdb_history -perf.data* - -**/dataset/ - -# Screenshots from samples etc. -screenshot*.png - -# Saved example `.rrd` files -example_data - -# Various builds -dist -wheels - -# Screenshot comparison build -/compare_screenshot - -.nox/ *.rrd -assets diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 0000000..aeeedaf --- /dev/null +++ b/.typos.toml @@ -0,0 +1,6 @@ +# https://github.com/crate-ci/typos +# install: cargo install typos-cli +# run: typos + +[default.extend-words] +teh = "teh" # part of @teh-cmc diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..1fbf07c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +opensource@rerun.io. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/Cranky.toml b/Cranky.toml new file mode 100644 index 0000000..ffcd35c --- /dev/null +++ b/Cranky.toml @@ -0,0 +1,165 @@ +# Copied from https://github.com/rerun-io/rerun_template +# +# https://github.com/ericseppanen/cargo-cranky +# cargo install cargo-cranky && cargo cranky +# See also clippy.toml + +deny = [ + "unsafe_code", + #"clippy::self_named_module_files", # Disabled waiting on https://github.com/rust-lang/rust-clippy/issues/9602 +] + +warn = [ + "clippy::all", + "clippy::as_ptr_cast_mut", + "clippy::await_holding_lock", + "clippy::bool_to_int_with_if", + "clippy::char_lit_as_u8", + "clippy::checked_conversions", + "clippy::clear_with_drain", + "clippy::cloned_instead_of_copied", + "clippy::cloned_instead_of_copied", + "clippy::dbg_macro", + "clippy::debug_assert_with_mut_call", + "clippy::derive_partial_eq_without_eq", + "clippy::disallowed_macros", # See clippy.toml + "clippy::disallowed_methods", # See clippy.toml + "clippy::disallowed_names", # See clippy.toml + "clippy::disallowed_script_idents", # See clippy.toml + "clippy::disallowed_types", # See clippy.toml + "clippy::doc_link_with_quotes", + "clippy::doc_markdown", + "clippy::empty_enum", + "clippy::enum_glob_use", + "clippy::equatable_if_let", + "clippy::exit", + "clippy::expl_impl_clone_on_copy", + "clippy::explicit_deref_methods", + "clippy::explicit_into_iter_loop", + "clippy::explicit_iter_loop", + "clippy::fallible_impl_from", + "clippy::filter_map_next", + "clippy::flat_map_option", + "clippy::float_cmp_const", + "clippy::fn_params_excessive_bools", + "clippy::fn_to_numeric_cast_any", + "clippy::from_iter_instead_of_collect", + "clippy::get_unwrap", + "clippy::if_let_mutex", + "clippy::implicit_clone", + "clippy::imprecise_flops", + "clippy::index_refutable_slice", + "clippy::inefficient_to_string", + "clippy::infinite_loop", + "clippy::into_iter_without_iter", + "clippy::invalid_upcast_comparisons", + "clippy::iter_not_returning_iterator", + "clippy::iter_on_empty_collections", + "clippy::iter_on_single_items", + "clippy::iter_over_hash_type", + "clippy::iter_without_into_iter", + "clippy::large_digit_groups", + "clippy::large_include_file", + "clippy::large_stack_arrays", + "clippy::large_stack_frames", + "clippy::large_types_passed_by_value", + "clippy::let_underscore_untyped", + "clippy::let_unit_value", + "clippy::linkedlist", + "clippy::lossy_float_literal", + "clippy::macro_use_imports", + "clippy::manual_assert", + "clippy::manual_clamp", + "clippy::manual_instant_elapsed", + "clippy::manual_let_else", + "clippy::manual_ok_or", + "clippy::manual_string_new", + "clippy::map_err_ignore", + "clippy::map_flatten", + "clippy::map_unwrap_or", + "clippy::match_on_vec_items", + "clippy::match_same_arms", + "clippy::match_wild_err_arm", + "clippy::match_wildcard_for_single_variants", + "clippy::mem_forget", + "clippy::mismatched_target_os", + "clippy::mismatching_type_param_order", + "clippy::missing_assert_message", + "clippy::missing_enforced_import_renames", + "clippy::missing_errors_doc", + "clippy::missing_safety_doc", + "clippy::mut_mut", + "clippy::mutex_integer", + "clippy::needless_borrow", + "clippy::needless_continue", + "clippy::needless_for_each", + "clippy::needless_pass_by_ref_mut", + "clippy::needless_pass_by_value", + "clippy::negative_feature_names", + "clippy::nonstandard_macro_braces", + "clippy::option_option", + "clippy::path_buf_push_overwrite", + "clippy::ptr_as_ptr", + "clippy::ptr_cast_constness", + "clippy::pub_without_shorthand", + "clippy::rc_mutex", + "clippy::readonly_write_lock", + "clippy::redundant_type_annotations", + "clippy::ref_option_ref", + "clippy::ref_patterns", + "clippy::rest_pat_in_fully_bound_structs", + "clippy::same_functions_in_if_condition", + "clippy::semicolon_if_nothing_returned", + "clippy::should_panic_without_expect", + "clippy::significant_drop_tightening", + "clippy::single_match_else", + "clippy::str_to_string", + "clippy::string_add_assign", + "clippy::string_add", + "clippy::string_lit_as_bytes", + "clippy::string_lit_chars_any", + "clippy::string_to_string", + "clippy::suspicious_command_arg_space", + "clippy::suspicious_xor_used_as_pow", + "clippy::todo", + "clippy::too_many_lines", + "clippy::trailing_empty_array", + "clippy::trait_duplication_in_bounds", + "clippy::tuple_array_conversions", + "clippy::unchecked_duration_subtraction", + "clippy::undocumented_unsafe_blocks", + "clippy::unimplemented", + "clippy::uninhabited_references", + "clippy::uninlined_format_args", + "clippy::unnecessary_box_returns", + "clippy::unnecessary_safety_doc", + "clippy::unnecessary_struct_initialization", + "clippy::unnecessary_wraps", + "clippy::unnested_or_patterns", + "clippy::unused_peekable", + "clippy::unused_rounding", + "clippy::unused_self", + "clippy::unwrap_used", + "clippy::useless_transmute", + "clippy::verbose_file_reads", + "clippy::wildcard_dependencies", + "clippy::wildcard_imports", + "clippy::zero_sized_map_values", + "elided_lifetimes_in_paths", + "future_incompatible", + "nonstandard_style", + "rust_2018_idioms", + "rust_2021_prelude_collisions", + "rustdoc::missing_crate_level_docs", + "semicolon_in_expressions_from_macros", + "trivial_numeric_casts", + "unsafe_op_in_unsafe_fn", # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668 + "unused_extern_crates", + "unused_import_braces", + "unused_lifetimes", + "unused_qualifications", +] + +allow = [ + "clippy::manual_range_contains", # this one is just worse imho +] diff --git a/LICENSE-MIT b/LICENSE-MIT index 79a5d00..3f6d1ed 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2022 Rerun Technologies AB +Copyright (c) 2024 Rerun Technologies AB Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/bacon.toml b/bacon.toml index 534d268..0476b3e 100644 --- a/bacon.toml +++ b/bacon.toml @@ -8,10 +8,63 @@ default_job = "cranky" [jobs.cranky] command = [ "cargo", - "clippy", + "cranky", "--all-targets", "--all-features", "--color=always", ] need_stdout = false watch = ["tests", "benches", "examples"] + +[jobs.check] +command = [ + "cargo", + "check", + "--all-targets", + "--all-features", + "--color=always", +] +need_stdout = false +watch = ["tests", "benches", "examples"] + +[jobs.test] +command = ["cargo", "test", "--color=always"] +need_stdout = true +watch = ["tests"] + +[jobs.doc] +command = ["cargo", "doc", "--color=always", "--all-features", "--no-deps"] +need_stdout = false + +# if the doc compiles, then it opens in your browser and bacon switches +# to the previous job +[jobs.doc-open] +command = [ + "cargo", + "doc", + "--color=always", + "--all-features", + "--no-deps", + "--open", +] +need_stdout = false +on_success = "back" # so that we don't open the browser at each change + +# You can run your application and have the result displayed in bacon, +# *if* it makes sense for this crate. You can run an example the same +# way. Don't forget the `--color always` part or the errors won't be +# properly parsed. +[jobs.run] +command = ["cargo", "run", "--color=always"] +need_stdout = true + +# You may define here keybindings that would be specific to +# a project, for example a shortcut to launch a specific job. +# Shortcuts to internal functions (scrolling, toggling, etc.) +# should go in your personal prefs.toml file instead. +[keybindings] +i = "job:initial" +c = "job:cranky" +d = "job:doc-open" +t = "job:test" +r = "job:run" diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..7e6ade7 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,79 @@ +# Copied from https://github.com/rerun-io/rerun_template +# +# There is also a scripts/clippy_wasm/clippy.toml which forbids some methods that are not available in wasm. + +# ----------------------------------------------------------------------------- +# Section identical to scripts/clippy_wasm/clippy.toml: + +msrv = "1.76" + +allow-unwrap-in-tests = true + +# https://doc.rust-lang.org/nightly/clippy/lint_configuration.html#avoid-breaking-exported-api +# We want suggestions, even if it changes public API. +avoid-breaking-exported-api = false + +excessive-nesting-threshold = 8 + +max-fn-params-bools = 1 + +# https://rust-lang.github.io/rust-clippy/master/index.html#/large_include_file +max-include-file-size = 1000000 + +# https://rust-lang.github.io/rust-clippy/master/index.html#/large_stack_frames +stack-size-threshold = 512000 + +too-many-lines-threshold = 200 + +# ----------------------------------------------------------------------------- + +# https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros +disallowed-macros = ['dbg'] + +# https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods +disallowed-methods = [ + { path = "egui_extras::TableBody::row", reason = "`row` doesn't scale. Use `rows` instead." }, + { path = "glam::Vec2::normalize", reason = "normalize() can create NaNs. Use try_normalize or normalize_or_zero" }, + { path = "glam::Vec3::normalize", reason = "normalize() can create NaNs. Use try_normalize or normalize_or_zero" }, + { path = "sha1::Digest::new", reason = "SHA1 is cryptographically broken" }, + { path = "std::env::temp_dir", reason = "Use the tempdir crate instead" }, + { path = "std::panic::catch_unwind", reason = "We compile with `panic = 'abort'`" }, + { path = "std::thread::spawn", reason = "Use `std::thread::Builder` and name the thread" }, + + # There are many things that aren't allowed on wasm, + # but we cannot disable them all here (because of e.g. https://github.com/rust-lang/rust-clippy/issues/10406) + # so we do that in `scripts/clippy_wasm/clippy.toml` instead. +] + +# https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names +disallowed-names = [] + +# https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types +disallowed-types = [ + { path = "ring::digest::SHA1_FOR_LEGACY_USE_ONLY", reason = "SHA1 is cryptographically broken" }, + + { path = "std::sync::Condvar", reason = "Use parking_lot instead" }, + { path = "std::sync::Mutex", reason = "Use parking_lot instead" }, + { path = "std::sync::RwLock", reason = "Use parking_lot instead" }, + + # "std::sync::Once", # enabled for now as the `log_once` macro uses it internally +] + +# Allow-list of words for markdown in dosctrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown +doc-valid-idents = [ + # You must also update the same list in `scripts/clippy_wasm/clippy.toml`! + "GitHub", + "GLB", + "GLTF", + "iOS", + "macOS", + "NaN", + "OBJ", + "OpenGL", + "PyPI", + "sRGB", + "sRGBA", + "WebGL", + "WebSocket", + "WebSockets", +] diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..8469881 --- /dev/null +++ b/deny.toml @@ -0,0 +1,101 @@ +# Copied from https://github.com/rerun-io/rerun_template +# +# https://github.com/EmbarkStudios/cargo-deny +# +# cargo-deny checks our dependency tree for copy-left licenses, +# duplicate dependencies, and rustsec advisories (https://rustsec.org/advisories). +# +# Install: `cargo install cargo-deny` +# Check: `cargo deny check`. + + +# Note: running just `cargo deny check` without a `--target` can result in +# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324 +[graph] +targets = [ + { triple = "aarch64-apple-darwin" }, + { triple = "i686-pc-windows-gnu" }, + { triple = "i686-pc-windows-msvc" }, + { triple = "i686-unknown-linux-gnu" }, + { triple = "wasm32-unknown-unknown" }, + { triple = "x86_64-apple-darwin" }, + { triple = "x86_64-pc-windows-gnu" }, + { triple = "x86_64-pc-windows-msvc" }, + { triple = "x86_64-unknown-linux-gnu" }, + { triple = "x86_64-unknown-linux-musl" }, + { triple = "x86_64-unknown-redox" }, +] +all-features = true + + +[advisories] +version = 2 +ignore = [] + + +[bans] +multiple-versions = "deny" +wildcards = "deny" +deny = [] +skip = [] +skip-tree = [ + # TODO(emilk): minimize duplicated dependencies + "async-fs", + "async-io", + "cargo_metadata", + "event-listener", + "glam", + "libloading", + "nix", + "objc2", + "raw-window-handle", # Small crate + "redox_syscall", + "regex-automata", + "regex-syntax", + "toml_edit", + "tracing-log", + "windows-sys", + "windows", +] + + +[licenses] +version = 2 +private = { ignore = true } +confidence-threshold = 0.93 # We want really high confidence when inferring licenses from text +allow = [ + "Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html + "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) + "BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) + "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) + "BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained + "CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/ + "ISC", # https://www.tldrlegal.com/license/isc-license + "LicenseRef-UFL-1.0", # See https://github.com/emilk/egui/issues/2321 + "MIT-0", # https://choosealicense.com/licenses/mit-0/ + "MIT", # https://tldrlegal.com/license/mit-license + "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux. + "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html + "OpenSSL", # https://www.openssl.org/source/license.html - used on Linux + "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html + "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) +] +exceptions = [] + +[[licenses.clarify]] +name = "webpki" +expression = "ISC" +license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] + +[[licenses.clarify]] +name = "ring" +expression = "MIT AND ISC AND OpenSSL" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + + +[sources] +unknown-registry = "deny" +unknown-git = "deny" + +[sources.allow-org] +github = ["emilk", "rerun-io"] diff --git a/lychee.toml b/lychee.toml new file mode 100644 index 0000000..36d3115 --- /dev/null +++ b/lychee.toml @@ -0,0 +1,82 @@ +# Copied from https://github.com/rerun-io/rerun_template + +################################################################################ +# Config for the link checker lychee. +# +# Download & learn more at: +# https://github.com/lycheeverse/lychee +# +# Example config: +# https://github.com/lycheeverse/lychee/blob/master/lychee.example.toml +# +# Run `lychee . --dump` to list all found links that are being checked. +# +# Note that by default lychee will only check markdown and html files, +# to check any other files you have to point to them explicitly, e.g.: +# `lychee **/*.rs` +# To make things worse, `exclude_path` is ignored for these globs, +# so local runs with lots of gitignored files will be slow. +# (https://github.com/lycheeverse/lychee/issues/1405) +# +# This unfortunately doesn't list anything for non-glob checks. +################################################################################ + +# Maximum number of concurrent link checks. +# Workaround for "too many open files" error on MacOS, see https://github.com/lycheeverse/lychee/issues/1248 +max_concurrency = 32 + +# Check links inside `` and `
` blocks as well as Markdown code blocks.
+include_verbatim = true
+
+# Proceed for server connections considered insecure (invalid TLS).
+insecure = true
+
+# Exclude these filesystem paths from getting checked.
+exclude_path = [
+  # Unfortunately lychee doesn't yet read .gitignore https://github.com/lycheeverse/lychee/issues/1331
+  # The following entries are there because of that:
+  ".git",
+  "__pycache__",
+  "_deps/",
+  ".pixi",
+  "build",
+  "target_ra",
+  "target_wasm",
+  "target",
+  "venv",
+]
+
+# Exclude URLs and mail addresses from checking (supports regex).
+exclude = [
+  # Skip speculative links
+  '.*?speculative-link',
+
+  # Strings with replacements.
+  '/__VIEWER_VERSION__/', # Replacement variable __VIEWER_VERSION__.
+  '/\$',                  # Replacement variable $.
+  '/GIT_HASH/',           # Replacement variable GIT_HASH.
+  '\{\}',                 # Ignore links with string interpolation.
+  '\$relpath\^',          # Relative paths as used by rerun_cpp's doc header.
+  '%7B.+%7D',             # Ignore strings that look like ready to use links but contain a replacement strings. The URL escaping is for '{.+}' (this seems to be needed for html embedded urls since lychee assumes they use this encoding).
+  '%7B%7D',               # Ignore links with string interpolation, escaped variant.
+
+  # Local links that require further setup.
+  'http://127.0.0.1',
+  'http://localhost',
+  'recording:/',      # rrd recording link.
+  'ws:/',
+  're_viewer.js',     # Build artifact that html is linking to.
+
+  # Api endpoints.
+  'https://fonts.googleapis.com/', # Font API entrypoint, not a link.
+  'https://fonts.gstatic.com/',    # Font API entrypoint, not a link.
+  'https://tel.rerun.io/',         # Analytics endpoint.
+
+  # Avoid rate limiting.
+  'https://crates.io/crates/.*',                  # Avoid crates.io rate-limiting
+  'https://github.com/rerun-io/rerun/commit/\.*', # Ignore links to our own commits (typically in changelog).
+  'https://github.com/rerun-io/rerun/pull/\.*',   # Ignore links to our own pull requests (typically in changelog).
+
+  # Used in rerun_template repo until the user search-replaces `revy`
+  'https://github.com/rerun-io/revy',
+]
diff --git a/scripts/generate_changelog.py b/scripts/generate_changelog.py
new file mode 100755
index 0000000..477382a
--- /dev/null
+++ b/scripts/generate_changelog.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+# Copied from https://github.com/rerun-io/rerun_template
+
+"""
+Summarizes recent PRs based on their GitHub labels.
+
+The result can be copy-pasted into CHANGELOG.md,
+though it often needs some manual editing too.
+"""
+
+from __future__ import annotations
+
+import argparse
+import multiprocessing
+import os
+import re
+import sys
+from dataclasses import dataclass
+from typing import Any, Optional
+
+import requests
+from git import Repo  # pip install GitPython
+from tqdm import tqdm
+
+OWNER = "rerun-io"
+REPO = "revy"
+INCLUDE_LABELS = False  # It adds quite a bit of visual noise
+OFFICIAL_RERUN_DEVS = [
+    "abey79",
+    "emilk",
+    "jleibs",
+    "jprochazk",
+    "nikolausWest",
+    "teh-cmc",
+    "Wumpf",
+]
+
+
+@dataclass
+class PrInfo:
+    gh_user_name: str
+    pr_title: str
+    labels: list[str]
+
+
+@dataclass
+class CommitInfo:
+    hexsha: str
+    title: str
+    pr_number: Optional[int]
+
+
+def get_github_token() -> str:
+    token = os.environ.get("GH_ACCESS_TOKEN", "")
+    if token != "":
+        return token
+
+    home_dir = os.path.expanduser("~")
+    token_file = os.path.join(home_dir, ".githubtoken")
+
+    try:
+        with open(token_file, encoding="utf8") as f:
+            token = f.read().strip()
+        return token
+    except Exception:
+        pass
+
+    print("ERROR: expected a GitHub token in the environment variable GH_ACCESS_TOKEN or in ~/.githubtoken")
+    sys.exit(1)
+
+
+# Slow
+def fetch_pr_info_from_commit_info(commit_info: CommitInfo) -> Optional[PrInfo]:
+    if commit_info.pr_number is None:
+        return None
+    else:
+        return fetch_pr_info(commit_info.pr_number)
+
+
+# Slow
+def fetch_pr_info(pr_number: int) -> Optional[PrInfo]:
+    url = f"https://api.github.com/repos/{OWNER}/{REPO}/pulls/{pr_number}"
+    gh_access_token = get_github_token()
+    headers = {"Authorization": f"Token {gh_access_token}"}
+    response = requests.get(url, headers=headers)
+    json = response.json()
+
+    # Check if the request was successful (status code 200)
+    if response.status_code == 200:
+        labels = [label["name"] for label in json["labels"]]
+        gh_user_name = json["user"]["login"]
+        return PrInfo(gh_user_name=gh_user_name, pr_title=json["title"], labels=labels)
+    else:
+        print(f"ERROR {url}: {response.status_code} - {json['message']}")
+        return None
+
+
+def get_commit_info(commit: Any) -> CommitInfo:
+    match = re.match(r"(.*) \(#(\d+)\)", commit.summary)
+    if match:
+        title = str(match.group(1))
+        pr_number = int(match.group(2))
+        return CommitInfo(hexsha=commit.hexsha, title=title, pr_number=pr_number)
+    else:
+        return CommitInfo(hexsha=commit.hexsha, title=commit.summary, pr_number=None)
+
+
+def remove_prefix(text: str, prefix: str) -> str:
+    if text.startswith(prefix):
+        return text[len(prefix) :]
+    return text  # or whatever
+
+
+def print_section(crate: str, items: list[str]) -> None:
+    if 0 < len(items):
+        print(f"#### {crate}")
+        for line in items:
+            print(f"* {line}")
+    print()
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(description="Generate a changelog.")
+    parser.add_argument("--commit-range", help="e.g. 0.1.0..HEAD", required=True)
+    args = parser.parse_args()
+
+    repo = Repo(".")
+    commits = list(repo.iter_commits(args.commit_range))
+    commits.reverse()  # Most recent last
+    commit_infos = list(map(get_commit_info, commits))
+
+    pool = multiprocessing.Pool()
+    pr_infos = list(
+        tqdm(
+            pool.imap(fetch_pr_info_from_commit_info, commit_infos),
+            total=len(commit_infos),
+            desc="Fetch PR info commits",
+        )
+    )
+
+    prs = []
+    unsorted_commits = []
+
+    for commit_info, pr_info in zip(commit_infos, pr_infos):
+        hexsha = commit_info.hexsha
+        title = commit_info.title
+        title = title.rstrip(".").strip()  # Some PR end with an unnecessary period
+        pr_number = commit_info.pr_number
+
+        if pr_number is None:
+            # Someone committed straight to main:
+            summary = f"{title} [{hexsha[:7]}](https://github.com/{OWNER}/{REPO}/commit/{hexsha})"
+            unsorted_commits.append(summary)
+        else:
+            # We prefer the PR title if available
+            title = pr_info.pr_title if pr_info else title
+            labels = pr_info.labels if pr_info else []
+
+            if "exclude from changelog" in labels:
+                continue
+            if "typo" in labels:
+                # We get so many typo PRs. Let's not flood the changelog with them.
+                continue
+
+            summary = f"{title} [#{pr_number}](https://github.com/{OWNER}/{REPO}/pull/{pr_number})"
+
+            if INCLUDE_LABELS and 0 < len(labels):
+                summary += f" ({', '.join(labels)})"
+
+            if pr_info is not None:
+                gh_user_name = pr_info.gh_user_name
+                if gh_user_name not in OFFICIAL_RERUN_DEVS:
+                    summary += f" (thanks [@{gh_user_name}](https://github.com/{gh_user_name})!)"
+
+            prs.append(summary)
+
+    # Clean up:
+    for i in range(len(prs)):
+        line = prs[i]
+        line = line[0].upper() + line[1:]  # Upper-case first letter
+        prs[i] = line
+
+    print()
+    print(f"Full diff at https://github.com/rerun-io/{REPO}/compare/{args.commit_range}")
+    print()
+    print_section("PRs", prs)
+    print_section("Unsorted commits", unsorted_commits)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/template_update.py b/scripts/template_update.py
new file mode 100755
index 0000000..85e3110
--- /dev/null
+++ b/scripts/template_update.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python3
+# Copied from https://github.com/rerun-io/rerun_template
+
+"""
+The script has two purposes.
+
+After using `rerun_template` as a template, run this to clean out things you don't need.
+Use `scripts/template_update.py init --languages cpp,rust,python` for this.
+
+Update an existing repository with the latest changes from the template.
+Use `scripts/template_update.py update --languages cpp,rust,python` for this.
+
+In either case, make sure the list of languages matches the languages you want to support.
+You can also use `--dry-run` to see what would happen without actually changing anything.
+"""
+
+from __future__ import annotations
+
+import argparse
+import os
+import shutil
+import tempfile
+
+from git import Repo
+
+OWNER = "rerun-io"
+
+# Files requires by C++, but not by both Python or Rust.
+CPP_FILES = {
+    ".clang-format",
+    ".github/workflows/cpp.yml",
+    "CMakeLists.txt",
+    "pixi.lock",  # Not needed by Rust
+    "pixi.toml",  # Not needed by Rust
+    "src/main.cpp",
+    "src/",
+}
+
+# Files requires by Python, but not by both C++ or Rust
+PYTHON_FILES = {
+    ".github/workflows/python.yml",
+    ".mypy.ini",
+    "main.py",
+    "pixi.lock",  # Not needed by Rust
+    "pixi.toml",  # Not needed by Rust
+    "pyproject.toml",
+    "requirements.txt",
+}
+
+# Files requires by Rust, but not by both C++ or Python
+RUST_FILES = {
+    ".github/workflows/rust.yml",
+    "bacon.toml",
+    "Cargo.lock",
+    "Cargo.toml",
+    "clippy.toml",
+    "Cranky.toml",
+    "deny.toml",
+    "rust-toolchain",
+    "scripts/clippy_wasm/",
+    "scripts/clippy_wasm/clippy.toml",
+    "src/lib.rs",
+    "src/main.rs",
+    "src/",
+}
+
+
+def parse_languages(lang_str: str) -> set[str]:
+    languages = lang_str.split(",") if lang_str else []
+    for lang in languages:
+        assert lang in ["cpp", "python", "rust"], f"Unsupported language: {lang}"
+    return set(languages)
+
+
+def calc_deny_set(languages: set[str]) -> set[str]:
+    """The set of files to delete/ignore."""
+    files_to_delete = CPP_FILES | PYTHON_FILES | RUST_FILES
+    if "cpp" in languages:
+        files_to_delete -= CPP_FILES
+    if "python" in languages:
+        files_to_delete -= PYTHON_FILES
+    if "rust" in languages:
+        files_to_delete -= RUST_FILES
+    return files_to_delete
+
+
+def init(languages: set[str], dry_run: bool) -> None:
+    print("Removing all language-specific files not needed for languages {languages}.")
+    files_to_delete = calc_deny_set(languages)
+    delete_files_and_folder(files_to_delete, dry_run)
+
+
+def delete_files_and_folder(paths: set[str], dry_run: bool) -> None:
+    repo_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+    for path in paths:
+        full_path = os.path.join(repo_path, path)
+        if os.path.exists(full_path):
+            if os.path.isfile(full_path):
+                print(f"Removing file {full_path}…")
+                if not dry_run:
+                    os.remove(full_path)
+            elif os.path.isdir(full_path):
+                print(f"Removing folder {full_path}…")
+                if not dry_run:
+                    shutil.rmtree(full_path)
+
+
+def update(languages: set[str], dry_run: bool) -> None:
+    # Don't overwrite these
+    ALWAYS_IGNORE_FILES = {"README.md", "pixi.lock", "Cargo.lock", "main.py", "requirements.txt"}
+
+    files_to_ignore = calc_deny_set(languages) | ALWAYS_IGNORE_FILES
+    repo_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+
+    with tempfile.TemporaryDirectory() as temp_dir:
+        Repo.clone_from("https://github.com/rerun-io/rerun_template.git", temp_dir)
+        for root, dirs, files in os.walk(temp_dir):
+            for file in files:
+                src_path = os.path.join(root, file)
+                rel_path = os.path.relpath(src_path, temp_dir)
+
+                if rel_path.startswith(".git/"):
+                    continue
+                if rel_path.startswith("src/"):
+                    continue
+                if rel_path in files_to_ignore:
+                    continue
+
+                dest_path = os.path.join(repo_path, rel_path)
+
+                print(f"Updating {rel_path}…")
+                if not dry_run:
+                    os.makedirs(os.path.dirname(dest_path), exist_ok=True)
+                    shutil.copy2(src_path, dest_path)
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(description="Handle the Rerun template.")
+    subparsers = parser.add_subparsers(dest="command")
+
+    init_parser = subparsers.add_parser("init", help="Initialize a new checkout of the template.")
+    init_parser.add_argument(
+        "--languages", default="", nargs="?", const="", help="The languages to support (e.g. `cpp,python,rust`)."
+    )
+    init_parser.add_argument("--dry-run", action="store_true", help="Don't actually delete any files.")
+
+    update_parser = subparsers.add_parser(
+        "update", help="Update all existing Rerun repositories with the latest changes from the template"
+    )
+    update_parser.add_argument(
+        "--languages", default="", nargs="?", const="", help="The languages to support (e.g. `cpp,python,rust`)."
+    )
+    update_parser.add_argument("--dry-run", action="store_true", help="Don't actually delete any files.")
+
+    args = parser.parse_args()
+
+    if args.command == "init":
+        init(parse_languages(args.languages), args.dry_run)
+    elif args.command == "update":
+        update(parse_languages(args.languages), args.dry_run)
+    else:
+        parser.print_help()
+        exit(1)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/src/default_loggers.rs b/src/default_loggers.rs
index d19f9fd..d1e15d0 100644
--- a/src/default_loggers.rs
+++ b/src/default_loggers.rs
@@ -12,7 +12,7 @@ use crate::{compute_entity_path, Aliased, RerunLogger, ToRerun};
 
 /// The default [`RerunLogger`]s that are used if no user-defined logger is specified.
 ///
-/// See [`RerunComponentLoggers`] for more information.
+/// See [`crate::RerunComponentLoggers`] for more information.
 ///
 /// Public so end users can easily inspect what is configured by default.
 #[derive(Resource, Deref, DerefMut, Clone, Debug)]
@@ -265,17 +265,17 @@ fn bevy_sprite<'w>(
                 })
                 .flatten()
                 .and_then(|tex| {
-                    tex.image_height_width_channels().map(|[w, h, _]| {
+                    tex.image_height_width_channels().and_then(|[w, h, _]| {
                         let mesh = PlaneMeshBuilder::default()
                             .normal(Direction3d::Z)
                             .size(w as _, h as _)
                             .build();
-                        mesh.to_rerun()
-                            .unwrap()
-                            .with_mesh_material(rerun::Material::from_albedo_factor(
+                        mesh.to_rerun().map(|mesh| {
+                            mesh.with_mesh_material(rerun::Material::from_albedo_factor(
                                 sprite.color.to_rerun().0,
                             ))
                             .with_albedo_texture(tex)
+                        })
                     })
                 })
         })
diff --git a/src/lib.rs b/src/lib.rs
index 4221cf5..1bc21c3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,7 +5,7 @@ use bevy::prelude::*;
 // TODO(cmc): support for bug report mode (buffering + kickoff)
 
 pub struct RerunPlugin {
-    pub rec: rerun::RecordingStream,
+    pub rec: RecordingStream,
 }
 
 impl Plugin for RerunPlugin {
diff --git a/src/rerun_logger.rs b/src/rerun_logger.rs
index 1bc2596..a46b10c 100644
--- a/src/rerun_logger.rs
+++ b/src/rerun_logger.rs
@@ -94,7 +94,7 @@ impl RerunLogger {
 /// Use `None` to prevent the data from being logged entirely.
 ///
 /// Don't set anything if you want to let the default logger to take over.
-/// See [`DefaultComponentLoggers`] for more information.
+/// See [`crate::DefaultRerunComponentLoggers`] for more information.
 ///
 /// If no default logger exists, the data will be logged as a [`rerun::TextDocument`].
 #[derive(Resource, Deref, DerefMut, Clone)]
@@ -218,7 +218,7 @@ impl Aliased {
 impl rerun::AsComponents for Aliased {
     #[inline]
     fn as_component_batches(&self) -> Vec> {
-        vec![rerun::MaybeOwnedComponentBatch::Ref(self as _)]
+        vec![rerun::MaybeOwnedComponentBatch::Ref(self)]
     }
 }
 
diff --git a/src/sync.rs b/src/sync.rs
index 75ffe8f..b870068 100644
--- a/src/sync.rs
+++ b/src/sync.rs
@@ -128,7 +128,7 @@ fn sync_components(
     fn collect_events(world: &mut World) -> Vec> {
         let events = world.resource_mut::>>();
         let mut reader = ManualEventReader::>::default();
-        reader.read(&events).cloned().collect()
+        reader.read(&events).copied().collect()
     }
     let image_events = collect_events::(world);
     let mesh_events = collect_events::(world);
@@ -161,10 +161,8 @@ fn sync_components(
             .get::()
             .unwrap_or(&empty_hashes);
 
-        let mut as_components: bevy::utils::HashMap<
-            Option<&'static str>,
-            Vec>,
-        > = Default::default();
+        let mut as_components: HashMap, Vec>> =
+            Default::default();
         let info = world.inspect_entity(entity_id);
         for component in &info {
             let mut has_changed = entity
@@ -302,7 +300,7 @@ fn component_to_hash(
                 // Safety: the type registry cannot be wrong, surely
                 .map(|ptr| unsafe { reflect_from_ptr.as_reflect(ptr) });
 
-            // TODO(cmc): `Reflect::reflect_hash` is basically never avaible so we go the long way
+            // TODO(cmc): `Reflect::reflect_hash` is basically never available so we go the long way
             // instead... this is likely waaay too costly in practice :)
             reflected.and_then(|reflected| {
                 let serializer = ReflectSerializer::new(reflected, &type_registry);