Add a simple CxxTarget type.

This allows access to GN's target object from a rust target.

Bug: 528225104
Change-Id: I88fca33a79ef82a40a5602b3ee6b6c4b6a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/25561
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Matt Stark <msta@google.com>
diff --git a/src/gn/ffi/bridge.cc b/src/gn/ffi/bridge.cc
index 2bef7a7..723f70d 100644
--- a/src/gn/ffi/bridge.cc
+++ b/src/gn/ffi/bridge.cc
@@ -3,6 +3,7 @@
 #include "gn/err.h"
 #include "gn/ffi/err.h"
 #include "gn/ffi/scope.h"
+#include "gn/ffi/target.h"
 #include "gn/ffi/test_with_scope.h"
 #include "gn/ffi/value.h"
 #include "gn/label.h"
@@ -10,6 +11,7 @@
 #include "gn/scope.h"
 #include "gn/settings.h"
 #include "gn/source_dir.h"
+#include "gn/target.h"
 #include "gn/test_with_scope.h"
 #include "gn/value.h"
 #include <array>
@@ -811,6 +813,7 @@
 using OutputFile = ::OutputFile;
 using SourceDir = ::SourceDir;
 using Label = ::Label;
+using Target = ::Target;
 using Settings = ::Settings;
 using Scope = ::Scope;
 using TestWithScope = ::TestWithScope;
@@ -966,6 +969,21 @@
   new (return$) ::rust::Str(::rust::cxx_to_rust((self.*name$)()));
 }
 
+void cxxbridge1$196$Target$label(::Target const &self, ::Label const **return$) noexcept {
+  ::Label const &(::Target::*label$)() const = &::Target::label;
+  new (return$) ::Label const *(&(self.*label$)());
+}
+
+::Settings const *cxxbridge1$196$Target$settings_cxx(::Target const &self) noexcept {
+  ::Settings const *(::Target::*settings_cxx$)() const = &::Target::settings;
+  return (self.*settings_cxx$)();
+}
+
+void cxxbridge1$196$register_dependency(::Target &target, ::rust::Str package, ::rust::Str name, ::rust::Str toolchain_package, ::rust::Str toolchain_name) noexcept {
+  void (*register_dependency$)(::Target &, ::rust::Str, ::rust::Str, ::rust::Str, ::rust::Str) = ::register_dependency;
+  register_dependency$(target, package, name, toolchain_package, toolchain_name);
+}
+
 void cxxbridge1$196$Settings$toolchain_label(::Settings const &self, ::Label const **return$) noexcept {
   ::Label const &(::Settings::*toolchain_label$)() const = &::Settings::toolchain_label;
   new (return$) ::Label const *(&(self.*toolchain_label$)());
diff --git a/src/gn/ffi/bridge.h b/src/gn/ffi/bridge.h
index dbd322d..f8b8a1d 100644
--- a/src/gn/ffi/bridge.h
+++ b/src/gn/ffi/bridge.h
@@ -4,6 +4,7 @@
 #include "gn/err.h"
 #include "gn/ffi/err.h"
 #include "gn/ffi/scope.h"
+#include "gn/ffi/target.h"
 #include "gn/ffi/test_with_scope.h"
 #include "gn/ffi/value.h"
 #include "gn/label.h"
@@ -11,6 +12,7 @@
 #include "gn/scope.h"
 #include "gn/settings.h"
 #include "gn/source_dir.h"
+#include "gn/target.h"
 #include "gn/test_with_scope.h"
 #include "gn/value.h"
 #include <array>
@@ -749,6 +751,7 @@
 using OutputFile = ::OutputFile;
 using SourceDir = ::SourceDir;
 using Label = ::Label;
+using Target = ::Target;
 using Settings = ::Settings;
 using Scope = ::Scope;
 using TestWithScope = ::TestWithScope;
diff --git a/src/gn/starlark/crates/ffi/src/bridge.rs b/src/gn/starlark/crates/ffi/src/bridge.rs
index 1c8bf00..23ed719 100644
--- a/src/gn/starlark/crates/ffi/src/bridge.rs
+++ b/src/gn/starlark/crates/ffi/src/bridge.rs
@@ -129,6 +129,7 @@
         include!("gn/err.h");
         include!("gn/ffi/err.h");
         include!("gn/ffi/scope.h");
+        include!("gn/ffi/target.h");
         include!("gn/ffi/test_with_scope.h");
         include!("gn/ffi/value.h");
         include!("gn/label.h");
@@ -136,6 +137,7 @@
         include!("gn/scope.h");
         include!("gn/settings.h");
         include!("gn/source_dir.h");
+        include!("gn/target.h");
         include!("gn/test_with_scope.h");
         include!("gn/value.h");
 
@@ -187,6 +189,19 @@
         #[cxx_return_type = "const std::string&"]
         pub fn name(self: &Label) -> &str;
 
+        #[rust_name = "CxxTarget"]
+        type Target;
+        pub(in crate::target) fn label(self: &CxxTarget) -> &Label;
+        #[rust_name = "settings_cxx"]
+        pub(in crate::target) fn settings(self: &CxxTarget) -> *const Settings;
+        pub(in crate::target) fn register_dependency(
+            target: Pin<&mut CxxTarget>,
+            package: &str,
+            name: &str,
+            toolchain_package: &str,
+            toolchain_name: &str,
+        );
+
         type Settings;
         pub(in crate::settings) fn toolchain_label(self: &Settings) -> &Label;
 
diff --git a/src/gn/starlark/crates/ffi/src/eval_context.rs b/src/gn/starlark/crates/ffi/src/eval_context.rs
index 19d8b3b..3e88e00 100644
--- a/src/gn/starlark/crates/ffi/src/eval_context.rs
+++ b/src/gn/starlark/crates/ffi/src/eval_context.rs
@@ -81,9 +81,7 @@
             .ok_or_else(|| Error::RequiresBzlFile.into())
     }
 
-    fn require_rule_impl(
-        &self,
-    ) -> starlark::Result<&mut types::CtxState<crate::target_ref::TargetRef>> {
+    fn require_rule_impl(&self) -> starlark::Result<&mut types::CtxState<crate::TargetRef>> {
         todo!()
     }
 }
diff --git a/src/gn/starlark/crates/ffi/src/lib.rs b/src/gn/starlark/crates/ffi/src/lib.rs
index 2d39a16..6a2ffa8 100644
--- a/src/gn/starlark/crates/ffi/src/lib.rs
+++ b/src/gn/starlark/crates/ffi/src/lib.rs
@@ -28,16 +28,19 @@
 mod session;
 mod settings;
 mod slice;
+mod target;
 mod target_ref;
 mod test_with_scope;
 mod value;
 
 pub use bridge::{
-    Err, KeyValue, Label, OutputFile, OwnedFrozenValue, Scope, Settings, SourceDir, Value,
-    ValueType,
+    CxxTarget, Err, KeyValue, Label, OutputFile, OwnedFrozenValue, Scope, Settings, SourceDir,
+    Value, ValueType,
 };
 pub use mutability::Immutable;
 pub use opaque::{NonOpaque, OpaqueSized};
 pub use session::Session;
 pub use slice::{OwnedSlice, Slice};
+pub use target::Target;
+pub use target_ref::TargetRef;
 pub use test_with_scope::TestWithScope;
diff --git a/src/gn/starlark/crates/ffi/src/target.rs b/src/gn/starlark/crates/ffi/src/target.rs
new file mode 100644
index 0000000..c0b2a00
--- /dev/null
+++ b/src/gn/starlark/crates/ffi/src/target.rs
@@ -0,0 +1,67 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::ptr::NonNull;
+
+#[derive(Copy, Clone)]
+pub struct Target {
+    // We maintain a 0-1 relationship between starlark Targets and rust targets.
+    // starlark targets store a reference to C++ targets, and C++ targets store an optional
+    // reference to starlark targets.
+    cxx: NonNull<crate::bridge::CxxTarget>,
+    // Note: This is not a lightweight reference type.
+    // Fields such as rules, attr, and providers will be added in the future.
+}
+
+impl std::ops::Deref for Target {
+    type Target = crate::bridge::CxxTarget;
+
+    fn deref(&self) -> &Self::Target {
+        // Safety: The C++ Target pointer is guaranteed to be valid and live for the
+        // duration of the build evaluation.
+        unsafe { self.cxx.as_ref() }
+    }
+}
+
+impl allocative::Allocative for Target {
+    fn visit<'a, 'b: 'a>(&self, visitor: &'a mut allocative::Visitor<'b>) {
+        let visitor = visitor.enter_self_sized::<Self>();
+        visitor.exit();
+    }
+}
+
+// Safety: Target pointers in GN are heap-allocated and thread-safe to transfer
+// across evaluation boundaries.
+unsafe impl Send for Target {}
+// Safety: Target pointers in GN are thread-safe to reference across evaluation
+// boundaries.
+unsafe impl Sync for Target {}
+
+impl crate::bridge::CxxTarget {
+    /// Returns the settings for the target.
+    pub fn settings(&self) -> &crate::Settings {
+        // Safety: Settings pointer is always valid and non-null on constructed Targets.
+        unsafe { self.settings_cxx().as_ref() }.unwrap()
+    }
+
+    /// Returns the toolchain label for the target.
+    pub fn toolchain(&self) -> types::LabelRef<'_> {
+        self.settings().toolchain_label().as_ref()
+    }
+
+    /// Registers a dependency on this target.
+    pub fn register_dependency(
+        self: std::pin::Pin<&mut Self>,
+        label: types::LabelRef<'_>,
+        toolchain: types::LabelRef<'_>,
+    ) {
+        crate::bridge::register_dependency(
+            self,
+            label.package().as_str(),
+            label.name(),
+            toolchain.package().as_str(),
+            toolchain.name(),
+        );
+    }
+}
diff --git a/src/gn/starlark/crates/ffi/src/target_ref.rs b/src/gn/starlark/crates/ffi/src/target_ref.rs
index 238a59f..9bc9c7e 100644
--- a/src/gn/starlark/crates/ffi/src/target_ref.rs
+++ b/src/gn/starlark/crates/ffi/src/target_ref.rs
@@ -3,16 +3,40 @@
 // found in the LICENSE file.
 
 use allocative::Allocative;
-use starlark::values::{AllocValue, Heap, ProvidesStaticType, StarlarkValue, Value};
+use starlark::values::{
+    AllocValue, Heap, ProvidesStaticType, StarlarkValue, Value, ValueLike as _,
+};
 use starlark_derive::{starlark_value, NoSerialize};
-use types::LabelRef;
+use types::{LabelRef, TargetRef as _};
 
-#[derive(Clone, Allocative, ProvidesStaticType, Debug, NoSerialize, PartialEq, Eq, Hash)]
-pub struct TargetRef;
+use crate::target::Target;
+
+#[derive(Clone, Copy, Allocative, ProvidesStaticType, NoSerialize)]
+pub struct TargetRef(pub(crate) &'static Target);
+
+impl PartialEq for TargetRef {
+    fn eq(&self, other: &Self) -> bool {
+        std::ptr::eq(self.0, other.0)
+    }
+}
+impl Eq for TargetRef {}
+
+impl std::hash::Hash for TargetRef {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        std::ptr::hash(self.0, state);
+    }
+}
+
+impl std::fmt::Debug for TargetRef {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        // Delegate to Display
+        write!(f, "{self}")
+    }
+}
 
 impl std::fmt::Display for TargetRef {
-    fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        todo!()
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.label())
     }
 }
 
@@ -20,15 +44,19 @@
 
 #[starlark_value(type = "Target")]
 impl<'v> StarlarkValue<'v> for TargetRef {
-    fn equals(&self, _other: Value<'v>) -> starlark::Result<bool> {
-        todo!()
+    fn equals(&self, other: Value<'v>) -> starlark::Result<bool> {
+        Ok(other
+            .downcast_ref::<Self>()
+            .is_some_and(|other| other == self))
     }
 
     fn write_hash(
         &self,
-        _hasher: &mut starlark::collections::StarlarkHasher,
+        hasher: &mut starlark::collections::StarlarkHasher,
     ) -> starlark::Result<()> {
-        todo!()
+        use std::hash::Hash as _;
+        self.hash(hasher);
+        Ok(())
     }
 }
 
@@ -42,11 +70,11 @@
     type Rule = rule::FrozenRule<crate::eval_context::EvalContext>;
 
     fn label(&self) -> LabelRef<'_> {
-        todo!()
+        self.0.label().as_ref()
     }
 
     fn toolchain(&self) -> LabelRef<'_> {
-        todo!()
+        self.0.toolchain()
     }
 
     fn rule(&self) -> Option<&'static Self::Rule> {