Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ repos:
rev: v6.2.0
hooks:
- id: reuse-lint-file
name: REUSE lint (SPDX headers on changed files)
# Include CMakeLists.txt and Dockerfile.xxx by matching those explicitly in the regex
files: \.(py|js|ts|cpp|c|h|hpp|cxx|hxx|cmake|md|yaml|yml|sh|fbs|toml|html)$|CMakeLists\.txt$|Dockerfile(\.[A-Za-z0-9_-]+)?$
Comment thread
jiwenc-nv marked this conversation as resolved.
name: REUSE licenses (source files and complete EgoPost subtree)
entry: python scripts/check_licenses.py --fix
args: [] # Clear the upstream hook's lint-file argument.
stages: [pre-commit]
pass_filenames: false
always_run: true
# Copyright year: ensure SPDX-FileCopyrightText includes current year (--fix updates in place)
- repo: local
hooks:
Expand Down
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@ If you cannot run a shell, use your search/glob tools on the pattern `**/AGENTS.

Optional context index: [`src/core/AGENTS.md`](src/core/AGENTS.md) (also on the ancestor walk—read it when working under `src/core/`).

## Product naming

- Spell the product name “Isaac Capture” in prose; avoid the ambiguous shorthand “Capture”.

## Component licensing

- Keep licensing summaries brief in README and CONTRIBUTING; automate routine
checks and updates rather than documenting manual header maintenance.

- Preserve root `LICENSE.md` verbatim as Apache-2.0; explain component exceptions
in README/CONTRIBUTING and local license files, so GitHub can recognize the default.
- EgoPost uses `LicenseRef-OpenMDW-1.1`; preserve upstream exceptions and notices.
Keep license-boundary changes separate from copyright-template and year-policy changes.
- Keep canonical texts in `LICENSES/` unmodified, including template placeholders;
do not prepend project copyright or SPDX headers or include them in header repair.
- Source hosting does not authorize wheel inclusion. Keep package discovery explicit
and verify license metadata/notices in each artifact before shipping a new component.

## CMake & include structure

When you create or edit a `CMakeLists.txt`, add or move a target, place a header,
Expand Down
38 changes: 9 additions & 29 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ Thanks for your interest in contributing! This document outlines how to build, t
- Follow PEP 8 for style.
- Use type annotations (PEP 484) and prefer formatted string literals (PEP 701, f-strings).

## License

- Contributions use the license of the component/file being changed: Apache-2.0 by default, OpenMDW-1.1 for EgoPost, with explicit third-party exceptions preserved. The DCO below applies to each contribution.

## Pre-commit

We use [pre-commit](https://pre-commit.com/) to run Python linters and formatters (including
Expand Down Expand Up @@ -50,31 +54,10 @@ ruff format .

### License headers

We use [REUSE](https://reuse.software/) to enforce SPDX license headers on source files. Pre-commit runs `reuse lint-file` on changed files (e.g. `.py`, `.cpp`, `.md`, `.yaml`):

1. **Required fields**: Each file must have `SPDX-FileCopyrightText` and `SPDX-License-Identifier`.
2. **License**: `SPDX-License-Identifier` must be `Apache-2.0` (full text in `LICENSES/Apache-2.0.txt`).

Example header:

```
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
```

To add or fix headers, install the [reuse tool](https://github.com/fsfe/reuse-tool)

```bash
uv tool install reuse
```

and run for example:

```bash
reuse annotate -t compact -l Apache-2.0 --skip-unrecognised -r path/to/file
```

REUSE does not auto-update the copyright year when you touch a file; include the current year when adding or editing headers.
Pre-commit automatically checks and updates license headers with [REUSE](https://reuse.software/).
It adds NVIDIA headers to unannotated first-party files and selects the license by
file location, preserving existing attribution and explicit third-party exceptions.
Review and stage any automatic edits, then commit again.

## CI
GitHub Actions builds the default targets and runs tests.
Expand All @@ -85,10 +68,7 @@ GitHub Actions builds the default targets and runs tests.
3. Describe motivation, changes, and testing in the PR
4. Link related issues

## License
- Your contributions are under the repository’s license unless stated otherwise

### Signing Your Work
## Signing Your Work

* We require that all contributors "sign-off" on their commits. This certifies that the contribution is your original work, or you have rights to submit it under the same license, or a compatible license.

Expand Down
4 changes: 2 additions & 2 deletions LICENSES/Apache-2.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,15 @@
APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2020 NVIDIA Corporation
Copyright [yyyy] [name of copyright owner]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
49 changes: 49 additions & 0 deletions LICENSES/LicenseRef-OpenMDW-1.1.txt
Comment thread
jiwenc-nv marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
OpenMDW License Agreement, version 1.1 (OpenMDW-1.1)

By exercising rights granted to you under this agreement, you accept and agree
to its terms.

As used in this agreement, "Model Materials" means the materials provided to
you under this agreement, consisting of: (1) one or more machine learning
models (including architecture and parameters); and (2) all related artifacts
(including associated data, documentation and software) that are provided to
you hereunder.

Subject to your compliance with this agreement, permission is hereby granted,
free of charge, to deal in the Model Materials without restriction, including
under all copyright, patent, database, and trade secret rights included or
embodied therein.

If you distribute any portion of the Model Materials, you shall retain in your
distribution (1) a copy of this agreement, and (2) all copyright notices and
other notices of origin included in the Model Materials that are applicable to
your distribution.

If you file, maintain, or voluntarily participate in a lawsuit against any
person or entity asserting that the Model Materials directly or indirectly
infringe any patent or copyright, then all rights and grants made to you
hereunder are terminated, unless that lawsuit was in response to a
corresponding lawsuit first brought against you.

This agreement does not impose any restrictions or obligations with respect to
any use, modification, or sharing of any outputs generated by using the Model
Materials.

THE MODEL MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE, NONINFRINGEMENT, ACCURACY, OR THE
ABSENCE OF LATENT OR OTHER DEFECTS OR ERRORS, WHETHER OR NOT DISCOVERABLE, ALL
TO THE GREATEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW.

YOU ARE SOLELY RESPONSIBLE FOR (1) CLEARING RIGHTS OF OTHER PERSONS THAT MAY
APPLY TO THE MODEL MATERIALS OR ANY USE THEREOF, INCLUDING WITHOUT LIMITATION
ANY PERSON'S COPYRIGHTS OR OTHER RIGHTS INCLUDED OR EMBODIED IN THE MODEL
MATERIALS; (2) OBTAINING ANY NECESSARY CONSENTS, PERMISSIONS OR OTHER RIGHTS
REQUIRED FOR ANY USE OF THE MODEL MATERIALS; OR (3) PERFORMING ANY DUE
DILIGENCE OR UNDERTAKING ANY OTHER INVESTIGATIONS INTO THE MODEL MATERIALS OR
ANYTHING INCORPORATED OR EMBODIED THEREIN.

IN NO EVENT SHALL THE PROVIDERS OF THE MODEL MATERIALS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MODEL MATERIALS, THE
USE THEREOF OR OTHER DEALINGS THEREIN.
2 changes: 2 additions & 0 deletions LICENSES/MIT.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
MIT License

Copyright (c) <year> <copyright holders>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,9 @@ Our [documentation page](https://nvidia.github.io/IsaacCapture) provides everyth
Isaac Teleop is design to work side by side with [NVIDIA Isaac Lab](https://github.com/isaac-sim/IsaacLab) starting with Isaac Lab [3.0 Beta release](https://github.com/isaac-sim/IsaacLab/releases/tag/v3.0.0-beta).

To get started, please refer to Isaac Lab's [Installation](https://isaac-sim.github.io/IsaacLab/develop/source/setup/installation/index.html) guide for more details. Then follow the [CloudXR teleoperation in Isaac Lab](https://isaac-sim.github.io/IsaacLab/develop/source/how-to/cloudxr_teleoperation.html) to get started with Teleoperation in Sim.

## Licensing

The default license is [Apache-2.0](LICENSE.md).
EgoPost is licensed under [OpenMDW-1.1](src/python/isaaccapture_egopost/LICENSE.md).
Third-party materials may carry their own licenses.
7 changes: 7 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,10 @@ path = "deps/third_party/chromium/LICENSE"
precedence = "override"
SPDX-FileCopyrightText = "Copyright 2015 The Chromium Authors"
SPDX-License-Identifier = "BSD-3-Clause"

# EgoPost's default never overrides upstream headers or sidecar licenses.
# The license-only scaffold has no covered files yet; the canonical text is reserved for imports.
[[annotations]]
path = "src/python/isaaccapture_egopost/**"
precedence = "closest"
SPDX-License-Identifier = "LicenseRef-OpenMDW-1.1"
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ description = "Isaac Teleop"
readme = "README.md"
requires-python = ">=3.11"
license = "Apache-2.0"
license-files = ["LICENSE.md"]
authors = [
{ name = "NVIDIA" },
]
Expand Down Expand Up @@ -102,6 +103,8 @@ package = false
isaacteleop = { path = "src/compat" }

[tool.scikit-build]
# EgoPost is hosted here but is not part of the Isaac Capture distribution.
sdist.exclude = ["src/python/isaaccapture_egopost/**", "LICENSES/LicenseRef-OpenMDW-1.1.txt"]
# Floor matches CMakeLists' cmake_minimum_required (3.24, for `cmake --fresh`).
# Ubuntu 22.04's apt CMake is 3.22 and no longer satisfies it, so scikit-build-core
# fetches CMake from PyPI there; the <4 cap keeps that fetch on the 3.x line rather
Expand Down
176 changes: 176 additions & 0 deletions scripts/check_licenses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Check and fix Isaac Capture source headers, including all EgoPost assets."""

import argparse
import re
import subprocess
import sys
from datetime import date
from pathlib import Path

from reuse.global_licensing import ReuseTOML
from reuse.project import Project

EGOPOST_SUBTREE = Path("src/python/isaaccapture_egopost")
EGOPOST_LICENSE = "LicenseRef-OpenMDW-1.1"
DEFAULT_LICENSE = "Apache-2.0"
COPYRIGHT_HOLDER = "NVIDIA CORPORATION & AFFILIATES. All rights reserved."
SOURCE_FILES = re.compile(
r"\.(py|js|ts|cpp|c|h|hpp|cxx|hxx|cmake|md|yaml|yml|sh|fbs|toml|html)$"
r"|CMakeLists\.txt$|Dockerfile(\.[A-Za-z0-9_-]+)?$"
)


def covered_paths(project: Project) -> list[Path]:
# Check source files repository-wide; EgoPost additionally includes all assets.
paths = (path.relative_to(project.root) for path in project.all_files())
return sorted(
path
for path in paths
if path.is_relative_to(EGOPOST_SUBTREE) or SOURCE_FILES.search(str(path))
)


def third_party_path(path: Path) -> bool:
# Directory exemptions follow the repository layout: third_party lives in deps.
return path.parts[0] == "deps"


def is_egopost_default(project: Project, path: Path, info) -> bool:
if info.source_type.value != "reuse-toml" or info.source_path != "REUSE.toml":
return False
annotation = ReuseTOML.from_file(project.root / "REUSE.toml").find_annotations_item(
path
)
# A subtree fallback is policy, not an explicit per-file exception.
return annotation is not None and f"{EGOPOST_SUBTREE}/**" in annotation.paths


def check_egopost_default(project: Project) -> bool:
annotations = ReuseTOML.from_file(project.root / "REUSE.toml").annotations
defaults = [item for item in annotations if f"{EGOPOST_SUBTREE}/**" in item.paths]
if not defaults or any(
{str(expr) for expr in item.spdx_expressions} != {EGOPOST_LICENSE}
or item.precedence.value != "closest"
for item in defaults
):
print(
f"REUSE.toml: EgoPost default must be {EGOPOST_LICENSE} with closest precedence"
)
return False
return True


def first_party_licenses(project: Project, path: Path) -> set[str] | None:
if third_party_path(path):
return None
infos = project.reuse_info_of(path)
notices = {notice.name for info in infos for notice in info.copyright_notices}
explicit_exception = any(
info.spdx_expressions
and info.source_type.value != "file-header"
and not is_egopost_default(project, path, info)
for info in infos
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (
explicit_exception
or not notices
or not all("NVIDIA CORPORATION & AFFILIATES" in notice for notice in notices)
):
return None
return {str(expr) for info in infos for expr in info.spdx_expressions}


def fix_license(project: Project, path: Path) -> bool:
if third_party_path(path):
return False
expected = (
EGOPOST_LICENSE if path.is_relative_to(EGOPOST_SUBTREE) else DEFAULT_LICENSE
)
infos = project.reuse_info_of(path)
notices = {notice for info in infos for notice in info.copyright_notices}
effective = {str(expr) for info in infos for expr in info.spdx_expressions}
expressions = first_party_licenses(project, path)
add_copyright = not notices and effective <= {expected}
if add_copyright or expressions == set():
command = [
sys.executable,
"-m",
"reuse",
"annotate",
"--fallback-dot-license",
"-l",
expected,
]
if add_copyright:
command += ["-c", COPYRIGHT_HOLDER, "--year", str(date.today().year)]
subprocess.run(command + [str(path)], check=True)
return True
if expressions is None:
return False
if expected == EGOPOST_LICENSE and expressions == {"Apache-2.0"}:
content = path.read_text()
# Replace only the default header tag, retaining copyright and other notices.
updated, count = re.subn(
r"(SPDX-License-Identifier:[ \t]*)Apache-2\.0(?=[ \t]*(?:\*/|-->)?[ \t]*$)",
rf"\g<1>{EGOPOST_LICENSE}",
content,
count=1,
flags=re.MULTILINE,
)
if count:
path.write_text(updated)
print(f"{path}: updated default license to {EGOPOST_LICENSE}")
return True
return False


def check_policy(project: Project, path: Path) -> bool:
expressions = first_party_licenses(project, path)
if expressions is not None and EGOPOST_LICENSE not in expressions:
print(
f"{path}: expected {EGOPOST_LICENSE}; document any license exception explicitly"
)
return False
return True


def main(fix: bool = False) -> int:
project = Project.from_directory(Path.cwd())
if not check_egopost_default(project):
return 1
paths = covered_paths(project)
changed = False
if fix:
for path in paths:
changed = fix_license(project, path) or changed
project = Project.from_directory(Path.cwd())
ok = True
# Pass complete coverage on every invocation, including metadata-only changes.
for start in range(0, len(paths), 200):
result = subprocess.run(
[sys.executable, "-m", "reuse", "lint-file", "--"]
+ [str(path) for path in paths[start : start + 200]],
check=False,
)
ok = result.returncode == 0 and ok
for path in paths:
if path.is_relative_to(EGOPOST_SUBTREE):
ok = check_policy(project, path) and ok
if changed:
print(
"License headers updated; review and stage the changes before committing."
)
return 0 if ok and not changed else 1


if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--fix",
action="store_true",
help="add default NVIDIA headers and repair default license tags",
)
sys.exit(main(fix=parser.parse_args().fix))
1 change: 1 addition & 0 deletions src/core/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ add_custom_target(python_package ALL
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/pyproject.toml" "${CMAKE_BINARY_DIR}/python_package/$<CONFIG>/"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/MANIFEST.in" "${CMAKE_BINARY_DIR}/python_package/$<CONFIG>/"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/setup.py" "${CMAKE_BINARY_DIR}/python_package/$<CONFIG>/"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/LICENSE.md" "${CMAKE_BINARY_DIR}/python_package/$<CONFIG>/LICENSE.md"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/python_package/$<CONFIG>/README.md"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt" "${CMAKE_BINARY_DIR}/python_package/$<CONFIG>/"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/requirements-dev.txt" "${CMAKE_BINARY_DIR}/python_package/$<CONFIG>/"
Expand Down
Loading
Loading