Add starlark files to depfile

Change-Id: I07a63af85ca4124e4344900e33819af06a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/27120
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Matt Stark <msta@google.com>
diff --git a/integration_tests/simple/goldens/build.ninja.d b/integration_tests/simple/goldens/build.ninja.d
index 84b21be..8b9a3e3 100644
--- a/integration_tests/simple/goldens/build.ninja.d
+++ b/integration_tests/simple/goldens/build.ninja.d
@@ -1 +1 @@
-build.ninja.stamp: ../.gn ../BUILD.gn ../build/BUILD.gn ../build/BUILDCONFIG.gn ../build/toolchain/BUILD.gn ./args.gn
\ No newline at end of file
+build.ninja.stamp: ../.gn ../BUILD.gn ../build/BUILD.gn ../build/BUILDCONFIG.gn ../build/toolchain/BUILD.gn ./args.gn
diff --git a/integration_tests/starlark/goldens/build.ninja.d b/integration_tests/starlark/goldens/build.ninja.d
index d66d296..f22feae 100644
--- a/integration_tests/starlark/goldens/build.ninja.d
+++ b/integration_tests/starlark/goldens/build.ninja.d
@@ -1 +1 @@
-build.ninja.stamp: ../.gn ../BUILD.gn ../build/BUILDCONFIG.gn ../build/toolchain/BUILD.gn ./args.gn
\ No newline at end of file
+build.ninja.stamp: ../.gn ../BUILD.gn ../build/BUILDCONFIG.gn ../build/toolchain/BUILD.gn ../filegroup.scl ./args.gn
diff --git a/src/gn/build_settings.h b/src/gn/build_settings.h
index 8c46a6a..08154b6 100644
--- a/src/gn/build_settings.h
+++ b/src/gn/build_settings.h
@@ -157,6 +157,7 @@
   }
 
   const Session& starlark_session() const;
+  bool has_starlark_session() const { return starlark_session_.has_value(); }
 
   // A list of target label patterns that can use allow_circular_includes_from.
   // If the returned pointer is null, allow_circular_includes_from may be used
diff --git a/src/gn/ffi/bridge.cc b/src/gn/ffi/bridge.cc
index 99a0f95..b500afc 100644
--- a/src/gn/ffi/bridge.cc
+++ b/src/gn/ffi/bridge.cc
@@ -17,10 +17,12 @@
 #include "gn/target.h"
 #include "gn/test_with_scope.h"
 #include "gn/value.h"
+#include <algorithm>
 #include <array>
 #include <cassert>
 #include <cstddef>
 #include <cstdint>
+#include <initializer_list>
 #include <iterator>
 #include <memory>
 #include <new>
@@ -665,6 +667,251 @@
 Box<T>::Box(uninit) noexcept {}
 #endif // CXXBRIDGE1_RUST_BOX
 
+#ifndef CXXBRIDGE1_RUST_BITCOPY_T
+#define CXXBRIDGE1_RUST_BITCOPY_T
+struct unsafe_bitcopy_t final {
+  explicit unsafe_bitcopy_t() = default;
+};
+#endif // CXXBRIDGE1_RUST_BITCOPY_T
+
+#ifndef CXXBRIDGE1_RUST_VEC
+#define CXXBRIDGE1_RUST_VEC
+template <typename T>
+class Vec final {
+public:
+  using value_type = T;
+
+  Vec() noexcept;
+  Vec(std::initializer_list<T>);
+  Vec(const Vec &);
+  Vec(Vec &&) noexcept;
+  ~Vec() noexcept;
+
+  Vec &operator=(Vec &&) & noexcept;
+  Vec &operator=(const Vec &) &;
+
+  std::size_t size() const noexcept;
+  bool empty() const noexcept;
+  const T *data() const noexcept;
+  T *data() noexcept;
+  std::size_t capacity() const noexcept;
+
+  const T &operator[](std::size_t n) const noexcept;
+  const T &at(std::size_t n) const;
+  const T &front() const noexcept;
+  const T &back() const noexcept;
+
+  T &operator[](std::size_t n) noexcept;
+  T &at(std::size_t n);
+  T &front() noexcept;
+  T &back() noexcept;
+
+  void reserve(std::size_t new_cap);
+  void push_back(const T &value);
+  void push_back(T &&value);
+  template <typename... Args>
+  void emplace_back(Args &&...args);
+  void truncate(std::size_t len);
+  void clear();
+
+  using iterator = typename Slice<T>::iterator;
+  iterator begin() noexcept;
+  iterator end() noexcept;
+
+  using const_iterator = typename Slice<const T>::iterator;
+  const_iterator begin() const noexcept;
+  const_iterator end() const noexcept;
+  const_iterator cbegin() const noexcept;
+  const_iterator cend() const noexcept;
+
+  void swap(Vec &) noexcept;
+
+  Vec(unsafe_bitcopy_t, const Vec &) noexcept;
+
+private:
+  void reserve_total(std::size_t new_cap) noexcept;
+  void set_len(std::size_t len) noexcept;
+  void drop() noexcept;
+
+  friend void swap(Vec &lhs, Vec &rhs) noexcept { lhs.swap(rhs); }
+
+  std::array<std::uintptr_t, 3> repr;
+};
+
+template <typename T>
+Vec<T>::Vec(std::initializer_list<T> init) : Vec{} {
+  this->reserve_total(init.size());
+  std::move(init.begin(), init.end(), std::back_inserter(*this));
+}
+
+template <typename T>
+Vec<T>::Vec(const Vec &other) : Vec() {
+  this->reserve_total(other.size());
+  std::copy(other.begin(), other.end(), std::back_inserter(*this));
+}
+
+template <typename T>
+Vec<T>::Vec(Vec &&other) noexcept : repr(other.repr) {
+  new (&other) Vec();
+}
+
+template <typename T>
+Vec<T>::~Vec() noexcept {
+  this->drop();
+}
+
+template <typename T>
+Vec<T> &Vec<T>::operator=(Vec &&other) & noexcept {
+  this->drop();
+  this->repr = other.repr;
+  new (&other) Vec();
+  return *this;
+}
+
+template <typename T>
+Vec<T> &Vec<T>::operator=(const Vec &other) & {
+  if (this != &other) {
+    this->drop();
+    new (this) Vec(other);
+  }
+  return *this;
+}
+
+template <typename T>
+bool Vec<T>::empty() const noexcept {
+  return this->size() == 0;
+}
+
+template <typename T>
+T *Vec<T>::data() noexcept {
+  return const_cast<T *>(const_cast<const Vec<T> *>(this)->data());
+}
+
+template <typename T>
+const T &Vec<T>::operator[](std::size_t n) const noexcept {
+  assert(n < this->size());
+  auto data = reinterpret_cast<const char *>(this->data());
+  return *reinterpret_cast<const T *>(data + n * size_of<T>());
+}
+
+template <typename T>
+const T &Vec<T>::at(std::size_t n) const {
+  if (n >= this->size()) {
+    panic<std::out_of_range>("rust::Vec index out of range");
+  }
+  return (*this)[n];
+}
+
+template <typename T>
+const T &Vec<T>::front() const noexcept {
+  assert(!this->empty());
+  return (*this)[0];
+}
+
+template <typename T>
+const T &Vec<T>::back() const noexcept {
+  assert(!this->empty());
+  return (*this)[this->size() - 1];
+}
+
+template <typename T>
+T &Vec<T>::operator[](std::size_t n) noexcept {
+  assert(n < this->size());
+  auto data = reinterpret_cast<char *>(this->data());
+  return *reinterpret_cast<T *>(data + n * size_of<T>());
+}
+
+template <typename T>
+T &Vec<T>::at(std::size_t n) {
+  if (n >= this->size()) {
+    panic<std::out_of_range>("rust::Vec index out of range");
+  }
+  return (*this)[n];
+}
+
+template <typename T>
+T &Vec<T>::front() noexcept {
+  assert(!this->empty());
+  return (*this)[0];
+}
+
+template <typename T>
+T &Vec<T>::back() noexcept {
+  assert(!this->empty());
+  return (*this)[this->size() - 1];
+}
+
+template <typename T>
+void Vec<T>::reserve(std::size_t new_cap) {
+  this->reserve_total(new_cap);
+}
+
+template <typename T>
+void Vec<T>::push_back(const T &value) {
+  this->emplace_back(value);
+}
+
+template <typename T>
+void Vec<T>::push_back(T &&value) {
+  this->emplace_back(std::move(value));
+}
+
+template <typename T>
+template <typename... Args>
+void Vec<T>::emplace_back(Args &&...args) {
+  auto size = this->size();
+  this->reserve_total(size + 1);
+  ::new (reinterpret_cast<T *>(reinterpret_cast<char *>(this->data()) +
+                               size * size_of<T>()))
+      T(std::forward<Args>(args)...);
+  this->set_len(size + 1);
+}
+
+template <typename T>
+void Vec<T>::clear() {
+  this->truncate(0);
+}
+
+template <typename T>
+typename Vec<T>::iterator Vec<T>::begin() noexcept {
+  return Slice<T>(this->data(), this->size()).begin();
+}
+
+template <typename T>
+typename Vec<T>::iterator Vec<T>::end() noexcept {
+  return Slice<T>(this->data(), this->size()).end();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::begin() const noexcept {
+  return this->cbegin();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::end() const noexcept {
+  return this->cend();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::cbegin() const noexcept {
+  return Slice<const T>(this->data(), this->size()).begin();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::cend() const noexcept {
+  return Slice<const T>(this->data(), this->size()).end();
+}
+
+template <typename T>
+void Vec<T>::swap(Vec &rhs) noexcept {
+  using std::swap;
+  swap(this->repr, rhs.repr);
+}
+
+template <typename T>
+Vec<T>::Vec(unsafe_bitcopy_t, const Vec &bits) noexcept : repr(bits.repr) {}
+#endif // CXXBRIDGE1_RUST_VEC
+
 #ifndef CXXBRIDGE1_RUST_OPAQUE
 #define CXXBRIDGE1_RUST_OPAQUE
 class Opaque {
@@ -781,11 +1028,11 @@
 template <>
 class impl<Str> final {
 public:
- static Str new_unchecked(repr::Fat repr) noexcept {
-   Str str = Str::uninit{};
-   str.repr = repr;
-   return str;
- }
+  static Str new_unchecked(repr::Fat repr) noexcept {
+    Str str = Str::uninit{};
+    str.repr = repr;
+    return str;
+  }
   static repr::Fat repr(Str str) noexcept {
     return str.repr;
   }
@@ -902,8 +1149,7 @@
 #ifndef CXXBRIDGE1_STRUCT_RustTarget
 #define CXXBRIDGE1_STRUCT_RustTarget
 struct RustTarget final : public ::rust::Opaque {
-  ::rust::Str execute_rule_impl(::Session const& session,
-                                ::Err& err) const noexcept;
+  ::rust::Str execute_rule_impl(::Session const &session, ::Err &err) const noexcept;
   ~RustTarget() = delete;
 
 private:
@@ -922,6 +1168,7 @@
   static ::rust::Box<::Session> new_for_testing() noexcept;
   ::RustTarget const &register_cxx_target(::Target const &target) const noexcept;
   void load_values(::rust::Str label, ::rust::Str relative_to, ::rust::Slice<::rust::Str const> keys, ::Scope &scope, ::Settings const &settings, ::ParseNodePtr origin, ::Err &err) const noexcept;
+  ::rust::Vec<::rust::Str> loaded() const noexcept;
   ~Session() = delete;
 
 private:
@@ -1062,12 +1309,9 @@
   new (return$) ::std::vector<::SourceFile> const *(&(self.*public_headers$)());
 }
 
-void cxxbridge1$196$Target$computed_outputs(
-    ::Target const& self,
-    ::std::vector<::OutputFile> const** return$) noexcept {
-  ::std::vector<::OutputFile> const& (::Target::*computed_outputs$)() const =
-      &::Target::computed_outputs;
-  new (return$)::std::vector<::OutputFile> const*(&(self.*computed_outputs$)());
+void cxxbridge1$196$Target$computed_outputs(::Target const &self, ::std::vector<::OutputFile> const **return$) noexcept {
+  ::std::vector<::OutputFile> const &(::Target::*computed_outputs$)() const = &::Target::computed_outputs;
+  new (return$) ::std::vector<::OutputFile> const *(&(self.*computed_outputs$)());
 }
 
 ::RustTarget const *cxxbridge1$196$Target$rust_target(::Target const &self, ::Session const &session) noexcept {
@@ -1232,8 +1476,7 @@
 ::std::size_t cxxbridge1$196$RustTarget$operator$sizeof() noexcept;
 ::std::size_t cxxbridge1$196$RustTarget$operator$alignof() noexcept;
 
-::rust::repr::Fat cxxbridge1$196$RustTarget$execute_rule_impl(
-    ::RustTarget const& self, ::Session const& session, ::Err& err) noexcept;
+::rust::repr::Fat cxxbridge1$196$RustTarget$execute_rule_impl(::RustTarget const &self, ::Session const &session, ::Err &err) noexcept;
 ::std::size_t cxxbridge1$196$Session$operator$sizeof() noexcept;
 ::std::size_t cxxbridge1$196$Session$operator$alignof() noexcept;
 
@@ -1244,6 +1487,8 @@
 ::RustTarget const *cxxbridge1$196$Session$register_cxx_target(::Session const &self, ::Target const &target) noexcept;
 
 void cxxbridge1$196$Session$load_values(::Session const &self, ::rust::Str label, ::rust::Str relative_to, ::rust::Slice<::rust::Str const> keys, ::Scope &scope, ::Settings const &settings, ::ParseNodePtr *origin, ::Err &err) noexcept;
+
+void cxxbridge1$196$Session$loaded(::Session const &self, ::rust::Vec<::rust::Str> *return$) noexcept;
 ::std::size_t cxxbridge1$196$OwnedFrozenValue$operator$sizeof() noexcept;
 ::std::size_t cxxbridge1$196$OwnedFrozenValue$operator$alignof() noexcept;
 
@@ -1264,10 +1509,8 @@
   return cxxbridge1$196$RustTarget$operator$alignof();
 }
 
-::rust::Str RustTarget::execute_rule_impl(::Session const& session,
-                                          ::Err& err) const noexcept {
-  return ::rust::impl<::rust::Str>::new_unchecked(
-      cxxbridge1$196$RustTarget$execute_rule_impl(*this, session, err));
+::rust::Str RustTarget::execute_rule_impl(::Session const &session, ::Err &err) const noexcept {
+  return ::rust::impl<::rust::Str>::new_unchecked(cxxbridge1$196$RustTarget$execute_rule_impl(*this, session, err));
 }
 
 ::std::size_t Session::layout::size() noexcept {
@@ -1295,6 +1538,12 @@
   cxxbridge1$196$Session$load_values(*this, label, relative_to, keys, scope, settings, &origin$.value, err);
 }
 
+::rust::Vec<::rust::Str> Session::loaded() const noexcept {
+  ::rust::MaybeUninit<::rust::Vec<::rust::Str>> return$;
+  cxxbridge1$196$Session$loaded(*this, &return$.value);
+  return ::std::move(return$.value);
+}
+
 ::std::size_t OwnedFrozenValue::layout::size() noexcept {
   return cxxbridge1$196$OwnedFrozenValue$operator$sizeof();
 }
@@ -1410,58 +1659,38 @@
   ::rust::deleter_if<::rust::detail::is_complete<::std::vector<::SourceFile>>::value>{}(ptr);
 }
 
-::std::vector<::OutputFile>* cxxbridge1$std$vector$OutputFile$new() noexcept {
+::std::vector<::OutputFile> *cxxbridge1$std$vector$OutputFile$new() noexcept {
   return new ::std::vector<::OutputFile>();
 }
-::std::size_t cxxbridge1$std$vector$OutputFile$size(
-    ::std::vector<::OutputFile> const& s) noexcept {
+::std::size_t cxxbridge1$std$vector$OutputFile$size(::std::vector<::OutputFile> const &s) noexcept {
   return s.size();
 }
-::std::size_t cxxbridge1$std$vector$OutputFile$capacity(
-    ::std::vector<::OutputFile> const& s) noexcept {
+::std::size_t cxxbridge1$std$vector$OutputFile$capacity(::std::vector<::OutputFile> const &s) noexcept {
   return s.capacity();
 }
-::OutputFile* cxxbridge1$std$vector$OutputFile$get_unchecked(
-    ::std::vector<::OutputFile>* s, ::std::size_t pos) noexcept {
+::OutputFile *cxxbridge1$std$vector$OutputFile$get_unchecked(::std::vector<::OutputFile> *s, ::std::size_t pos) noexcept {
   return &(*s)[pos];
 }
-bool cxxbridge1$std$vector$OutputFile$reserve(::std::vector<::OutputFile>* s,
-                                              ::std::size_t new_cap) noexcept {
+bool cxxbridge1$std$vector$OutputFile$reserve(::std::vector<::OutputFile> *s, ::std::size_t new_cap) noexcept {
   return ::rust::if_move_constructible<::OutputFile>::reserve(*s, new_cap);
 }
-static_assert(
-    ::rust::detail::is_complete<
-        ::std::remove_extent<::std::vector<::OutputFile>>::type>::value,
-    "definition of `::std::vector<::OutputFile>` is required");
-static_assert(sizeof(::std::unique_ptr<::std::vector<::OutputFile>>) ==
-                  sizeof(void*),
-              "");
-static_assert(alignof(::std::unique_ptr<::std::vector<::OutputFile>>) ==
-                  alignof(void*),
-              "");
-void cxxbridge1$unique_ptr$std$vector$OutputFile$null(
-    ::std::unique_ptr<::std::vector<::OutputFile>>* ptr) noexcept {
-  ::new (ptr)::std::unique_ptr<::std::vector<::OutputFile>>();
+static_assert(::rust::detail::is_complete<::std::remove_extent<::std::vector<::OutputFile>>::type>::value, "definition of `::std::vector<::OutputFile>` is required");
+static_assert(sizeof(::std::unique_ptr<::std::vector<::OutputFile>>) == sizeof(void *), "");
+static_assert(alignof(::std::unique_ptr<::std::vector<::OutputFile>>) == alignof(void *), "");
+void cxxbridge1$unique_ptr$std$vector$OutputFile$null(::std::unique_ptr<::std::vector<::OutputFile>> *ptr) noexcept {
+  ::new (ptr) ::std::unique_ptr<::std::vector<::OutputFile>>();
 }
-void cxxbridge1$unique_ptr$std$vector$OutputFile$raw(
-    ::std::unique_ptr<::std::vector<::OutputFile>>* ptr,
-    ::std::unique_ptr<::std::vector<::OutputFile>>::pointer raw) noexcept {
-  ::new (ptr)::std::unique_ptr<::std::vector<::OutputFile>>(raw);
+void cxxbridge1$unique_ptr$std$vector$OutputFile$raw(::std::unique_ptr<::std::vector<::OutputFile>> *ptr, ::std::unique_ptr<::std::vector<::OutputFile>>::pointer raw) noexcept {
+  ::new (ptr) ::std::unique_ptr<::std::vector<::OutputFile>>(raw);
 }
-::std::unique_ptr<::std::vector<::OutputFile>>::element_type const*
-cxxbridge1$unique_ptr$std$vector$OutputFile$get(
-    ::std::unique_ptr<::std::vector<::OutputFile>> const& ptr) noexcept {
+::std::unique_ptr<::std::vector<::OutputFile>>::element_type const *cxxbridge1$unique_ptr$std$vector$OutputFile$get(::std::unique_ptr<::std::vector<::OutputFile>> const &ptr) noexcept {
   return ptr.get();
 }
-::std::unique_ptr<::std::vector<::OutputFile>>::pointer
-cxxbridge1$unique_ptr$std$vector$OutputFile$release(
-    ::std::unique_ptr<::std::vector<::OutputFile>>& ptr) noexcept {
+::std::unique_ptr<::std::vector<::OutputFile>>::pointer cxxbridge1$unique_ptr$std$vector$OutputFile$release(::std::unique_ptr<::std::vector<::OutputFile>> &ptr) noexcept {
   return ptr.release();
 }
-void cxxbridge1$unique_ptr$std$vector$OutputFile$drop(
-    ::std::unique_ptr<::std::vector<::OutputFile>>* ptr) noexcept {
-  ::rust::deleter_if<
-      ::rust::detail::is_complete<::std::vector<::OutputFile>>::value>{}(ptr);
+void cxxbridge1$unique_ptr$std$vector$OutputFile$drop(::std::unique_ptr<::std::vector<::OutputFile>> *ptr) noexcept {
+  ::rust::deleter_if<::rust::detail::is_complete<::std::vector<::OutputFile>>::value>{}(ptr);
 }
 
 static_assert(::rust::detail::is_complete<::std::remove_extent<::Scope>::type>::value, "definition of `::Scope` is required");
diff --git a/src/gn/ffi/bridge.h b/src/gn/ffi/bridge.h
index 85884f6..0ad1e9a 100644
--- a/src/gn/ffi/bridge.h
+++ b/src/gn/ffi/bridge.h
@@ -18,10 +18,12 @@
 #include "gn/target.h"
 #include "gn/test_with_scope.h"
 #include "gn/value.h"
+#include <algorithm>
 #include <array>
 #include <cassert>
 #include <cstddef>
 #include <cstdint>
+#include <initializer_list>
 #include <iterator>
 #include <memory>
 #include <new>
@@ -658,6 +660,251 @@
 Box<T>::Box(uninit) noexcept {}
 #endif // CXXBRIDGE1_RUST_BOX
 
+#ifndef CXXBRIDGE1_RUST_BITCOPY_T
+#define CXXBRIDGE1_RUST_BITCOPY_T
+struct unsafe_bitcopy_t final {
+  explicit unsafe_bitcopy_t() = default;
+};
+#endif // CXXBRIDGE1_RUST_BITCOPY_T
+
+#ifndef CXXBRIDGE1_RUST_VEC
+#define CXXBRIDGE1_RUST_VEC
+template <typename T>
+class Vec final {
+public:
+  using value_type = T;
+
+  Vec() noexcept;
+  Vec(std::initializer_list<T>);
+  Vec(const Vec &);
+  Vec(Vec &&) noexcept;
+  ~Vec() noexcept;
+
+  Vec &operator=(Vec &&) & noexcept;
+  Vec &operator=(const Vec &) &;
+
+  std::size_t size() const noexcept;
+  bool empty() const noexcept;
+  const T *data() const noexcept;
+  T *data() noexcept;
+  std::size_t capacity() const noexcept;
+
+  const T &operator[](std::size_t n) const noexcept;
+  const T &at(std::size_t n) const;
+  const T &front() const noexcept;
+  const T &back() const noexcept;
+
+  T &operator[](std::size_t n) noexcept;
+  T &at(std::size_t n);
+  T &front() noexcept;
+  T &back() noexcept;
+
+  void reserve(std::size_t new_cap);
+  void push_back(const T &value);
+  void push_back(T &&value);
+  template <typename... Args>
+  void emplace_back(Args &&...args);
+  void truncate(std::size_t len);
+  void clear();
+
+  using iterator = typename Slice<T>::iterator;
+  iterator begin() noexcept;
+  iterator end() noexcept;
+
+  using const_iterator = typename Slice<const T>::iterator;
+  const_iterator begin() const noexcept;
+  const_iterator end() const noexcept;
+  const_iterator cbegin() const noexcept;
+  const_iterator cend() const noexcept;
+
+  void swap(Vec &) noexcept;
+
+  Vec(unsafe_bitcopy_t, const Vec &) noexcept;
+
+private:
+  void reserve_total(std::size_t new_cap) noexcept;
+  void set_len(std::size_t len) noexcept;
+  void drop() noexcept;
+
+  friend void swap(Vec &lhs, Vec &rhs) noexcept { lhs.swap(rhs); }
+
+  std::array<std::uintptr_t, 3> repr;
+};
+
+template <typename T>
+Vec<T>::Vec(std::initializer_list<T> init) : Vec{} {
+  this->reserve_total(init.size());
+  std::move(init.begin(), init.end(), std::back_inserter(*this));
+}
+
+template <typename T>
+Vec<T>::Vec(const Vec &other) : Vec() {
+  this->reserve_total(other.size());
+  std::copy(other.begin(), other.end(), std::back_inserter(*this));
+}
+
+template <typename T>
+Vec<T>::Vec(Vec &&other) noexcept : repr(other.repr) {
+  new (&other) Vec();
+}
+
+template <typename T>
+Vec<T>::~Vec() noexcept {
+  this->drop();
+}
+
+template <typename T>
+Vec<T> &Vec<T>::operator=(Vec &&other) & noexcept {
+  this->drop();
+  this->repr = other.repr;
+  new (&other) Vec();
+  return *this;
+}
+
+template <typename T>
+Vec<T> &Vec<T>::operator=(const Vec &other) & {
+  if (this != &other) {
+    this->drop();
+    new (this) Vec(other);
+  }
+  return *this;
+}
+
+template <typename T>
+bool Vec<T>::empty() const noexcept {
+  return this->size() == 0;
+}
+
+template <typename T>
+T *Vec<T>::data() noexcept {
+  return const_cast<T *>(const_cast<const Vec<T> *>(this)->data());
+}
+
+template <typename T>
+const T &Vec<T>::operator[](std::size_t n) const noexcept {
+  assert(n < this->size());
+  auto data = reinterpret_cast<const char *>(this->data());
+  return *reinterpret_cast<const T *>(data + n * size_of<T>());
+}
+
+template <typename T>
+const T &Vec<T>::at(std::size_t n) const {
+  if (n >= this->size()) {
+    panic<std::out_of_range>("rust::Vec index out of range");
+  }
+  return (*this)[n];
+}
+
+template <typename T>
+const T &Vec<T>::front() const noexcept {
+  assert(!this->empty());
+  return (*this)[0];
+}
+
+template <typename T>
+const T &Vec<T>::back() const noexcept {
+  assert(!this->empty());
+  return (*this)[this->size() - 1];
+}
+
+template <typename T>
+T &Vec<T>::operator[](std::size_t n) noexcept {
+  assert(n < this->size());
+  auto data = reinterpret_cast<char *>(this->data());
+  return *reinterpret_cast<T *>(data + n * size_of<T>());
+}
+
+template <typename T>
+T &Vec<T>::at(std::size_t n) {
+  if (n >= this->size()) {
+    panic<std::out_of_range>("rust::Vec index out of range");
+  }
+  return (*this)[n];
+}
+
+template <typename T>
+T &Vec<T>::front() noexcept {
+  assert(!this->empty());
+  return (*this)[0];
+}
+
+template <typename T>
+T &Vec<T>::back() noexcept {
+  assert(!this->empty());
+  return (*this)[this->size() - 1];
+}
+
+template <typename T>
+void Vec<T>::reserve(std::size_t new_cap) {
+  this->reserve_total(new_cap);
+}
+
+template <typename T>
+void Vec<T>::push_back(const T &value) {
+  this->emplace_back(value);
+}
+
+template <typename T>
+void Vec<T>::push_back(T &&value) {
+  this->emplace_back(std::move(value));
+}
+
+template <typename T>
+template <typename... Args>
+void Vec<T>::emplace_back(Args &&...args) {
+  auto size = this->size();
+  this->reserve_total(size + 1);
+  ::new (reinterpret_cast<T *>(reinterpret_cast<char *>(this->data()) +
+                               size * size_of<T>()))
+      T(std::forward<Args>(args)...);
+  this->set_len(size + 1);
+}
+
+template <typename T>
+void Vec<T>::clear() {
+  this->truncate(0);
+}
+
+template <typename T>
+typename Vec<T>::iterator Vec<T>::begin() noexcept {
+  return Slice<T>(this->data(), this->size()).begin();
+}
+
+template <typename T>
+typename Vec<T>::iterator Vec<T>::end() noexcept {
+  return Slice<T>(this->data(), this->size()).end();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::begin() const noexcept {
+  return this->cbegin();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::end() const noexcept {
+  return this->cend();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::cbegin() const noexcept {
+  return Slice<const T>(this->data(), this->size()).begin();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::cend() const noexcept {
+  return Slice<const T>(this->data(), this->size()).end();
+}
+
+template <typename T>
+void Vec<T>::swap(Vec &rhs) noexcept {
+  using std::swap;
+  swap(this->repr, rhs.repr);
+}
+
+template <typename T>
+Vec<T>::Vec(unsafe_bitcopy_t, const Vec &bits) noexcept : repr(bits.repr) {}
+#endif // CXXBRIDGE1_RUST_VEC
+
 #ifndef CXXBRIDGE1_RUST_OPAQUE
 #define CXXBRIDGE1_RUST_OPAQUE
 class Opaque {
@@ -820,8 +1067,7 @@
 #ifndef CXXBRIDGE1_STRUCT_RustTarget
 #define CXXBRIDGE1_STRUCT_RustTarget
 struct RustTarget final : public ::rust::Opaque {
-  ::rust::Str execute_rule_impl(::Session const& session,
-                                ::Err& err) const noexcept;
+  ::rust::Str execute_rule_impl(::Session const &session, ::Err &err) const noexcept;
   ~RustTarget() = delete;
 
 private:
@@ -840,6 +1086,7 @@
   static ::rust::Box<::Session> new_for_testing() noexcept;
   ::RustTarget const &register_cxx_target(::Target const &target) const noexcept;
   void load_values(::rust::Str label, ::rust::Str relative_to, ::rust::Slice<::rust::Str const> keys, ::Scope &scope, ::Settings const &settings, ::ParseNodePtr origin, ::Err &err) const noexcept;
+  ::rust::Vec<::rust::Str> loaded() const noexcept;
   ~Session() = delete;
 
 private:
diff --git a/src/gn/ffi/session.cc b/src/gn/ffi/session.cc
index b2329bf..7143c6b 100644
--- a/src/gn/ffi/session.cc
+++ b/src/gn/ffi/session.cc
@@ -9,6 +9,7 @@
 #include "gn/build_settings.h"
 #include "gn/err.h"
 #include "gn/ffi/bridge.h"
+#include "gn/filesystem_utils.h"
 #include "gn/functions.h"
 #include "gn/parse_tree.h"
 #include "gn/scope.h"
@@ -76,3 +77,12 @@
 
   return !err.has_error();
 }
+
+void add_all_loaded_files(const Session& session,
+                          std::vector<base::FilePath>& files) {
+  const auto& loaded = session.loaded();
+  files.reserve(files.size() + loaded.size());
+  for (const auto& file : loaded) {
+    files.push_back(UTF8ToFilePath(std::string_view(file)));
+  }
+}
\ No newline at end of file
diff --git a/src/gn/ffi/session.h b/src/gn/ffi/session.h
index 75839c2..56df842 100644
--- a/src/gn/ffi/session.h
+++ b/src/gn/ffi/session.h
@@ -6,6 +6,9 @@
 #define TOOLS_GN_FFI_SESSION_H_
 
 #include <span>
+#include <vector>
+
+#include "base/files/file_path.h"
 
 class Err;
 class ParseNode;
@@ -35,4 +38,10 @@
                   ParseNodePtr parse_node,
                   Err& err);
 
+// Collects all files loaded by the session and adds them to files.
+// Note that this specifically refers to *starlark* files.
+// Returned file paths will be relaltive and normalized to '/' separators.
+void add_all_loaded_files(const Session& session,
+                          std::vector<base::FilePath>& files);
+
 #endif  // TOOLS_GN_FFI_SESSION_H_
\ No newline at end of file
diff --git a/src/gn/input_file_manager.cc b/src/gn/input_file_manager.cc
index 6e2f072..83e9c6d 100644
--- a/src/gn/input_file_manager.cc
+++ b/src/gn/input_file_manager.cc
@@ -264,13 +264,17 @@
   return static_cast<int>(input_files_.size());
 }
 
-void InputFileManager::AddAllPhysicalInputFileNamesToVectorSetSorter(
-    VectorSetSorter<base::FilePath>* sorter) const {
+void InputFileManager::AddAllPhysicalInputFiles(
+    std::vector<base::FilePath>& files) const {
   std::lock_guard<std::mutex> lock(lock_);
 
+  files.reserve(files.size() + input_files_.size());
+
   for (const auto& file : input_files_) {
-    if (!file.second->file.physical_name().empty())
-      sorter->Add(file.second->file.physical_name());
+    const base::FilePath& physical = file.second->file.physical_name();
+    if (!physical.empty()) {
+      files.push_back(physical);
+    }
   }
 }
 
diff --git a/src/gn/input_file_manager.h b/src/gn/input_file_manager.h
index 5d82eab..b202250 100644
--- a/src/gn/input_file_manager.h
+++ b/src/gn/input_file_manager.h
@@ -91,16 +91,8 @@
   // Does not count dynamic input.
   int GetInputFileCount() const;
 
-  // Add all physical input files to a VectorSetSorter instance.
-  // This allows fast merging and sorting with other file paths sets.
-  //
-  // This is more memory efficient than returning a vector of base::FilePath
-  // instance, especially with projects with a very large number of input files,
-  // but note that the VectorSetSorter only holds pointers to the
-  // items recorded in this InputFileManager instance, and it is up to the
-  // caller to ensure these will not change until the sorter is destroyed.
-  void AddAllPhysicalInputFileNamesToVectorSetSorter(
-      VectorSetSorter<base::FilePath>* sorter) const;
+  // Adds all physical input files to the given vector.
+  void AddAllPhysicalInputFiles(std::vector<base::FilePath>& files) const;
 
   void set_load_file_callback(SyncLoadFileCallback load_file_callback) {
     load_file_callback_ = load_file_callback;
diff --git a/src/gn/json_project_writer.cc b/src/gn/json_project_writer.cc
index 79df92e..8501561 100644
--- a/src/gn/json_project_writer.cc
+++ b/src/gn/json_project_writer.cc
@@ -13,16 +13,20 @@
 #include "base/files/file_path.h"
 #include "base/json/json_writer.h"
 #include "base/json/string_escape.h"
+#include "gn/build_settings.h"
 #include "gn/builder.h"
 #include "gn/commands.h"
 #include "gn/deps_iterator.h"
 #include "gn/desc_builder.h"
+#include "gn/ffi/bridge.h"
+#include "gn/ffi/session.h"
 #include "gn/filesystem_utils.h"
 #include "gn/invoke_python.h"
 #include "gn/resolved_target_data.h"
 #include "gn/scheduler.h"
 #include "gn/settings.h"
 #include "gn/string_output_buffer.h"
+#include "gn/value.h"
 #include "util/worker_pool.h"
 
 // Structure of JSON output file
@@ -358,27 +362,42 @@
 
     // Other files read by the build.
     std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies();
+    g_scheduler->input_file_manager()->AddAllPhysicalInputFiles(other_files);
 
-    const InputFileManager* input_file_manager =
-        g_scheduler->input_file_manager();
-
-    VectorSetSorter<base::FilePath> sorter(
-        input_file_manager->GetInputFileCount() + other_files.size());
-
-    input_file_manager->AddAllPhysicalInputFileNamesToVectorSetSorter(&sorter);
-
-    sorter.Add(other_files.begin(), other_files.end());
-
-    std::string build_path = FilePathToUTF8(build_settings->root_path());
-    auto item_callback = [&json_writer,
-                          &build_path](const base::FilePath& input_file) {
+    std::vector<SourceFile> files;
+    const std::string root_path = FilePathToUTF8(build_settings->root_path());
+    for (const auto& other_file : other_files) {
       std::string file;
       if (MakeAbsolutePathRelativeIfPossible(
-              build_path, FilePathToUTF8(input_file), &file)) {
-        json_writer.AddListItem(file);
+              root_path, FilePathToUTF8(other_file), &file)) {
+        files.emplace_back(std::move(file));
       }
-    };
-    sorter.IterateOver(item_callback);
+    }
+
+    if (build_settings->has_starlark_session()) {
+      std::vector<base::FilePath> loaded_files;
+      add_all_loaded_files(build_settings->starlark_session(), loaded_files);
+      for (const auto& loaded_file : loaded_files) {
+        Err err;
+        SourceFile source_file =
+            build_settings->build_dir().ResolveRelativeFile(
+                Value(nullptr, FilePathToUTF8(loaded_file)), &err,
+                build_settings->root_path_utf8());
+        // Starlark intentionally doesn't support loading files outside of the
+        // source root.
+        DCHECK(!err.has_error());
+        DCHECK(source_file.is_source_absolute());
+        files.push_back(std::move(source_file));
+      }
+    }
+
+    std::ranges::sort(files);
+    auto [first, last] = std::ranges::unique(files);
+    files.erase(first, last);
+
+    for (const auto& file : files) {
+      json_writer.AddListItem(file.value());
+    }
 
     json_writer.EndList();  // gen_input_files
 
diff --git a/src/gn/ninja_build_writer.cc b/src/gn/ninja_build_writer.cc
index 12e323f..b298f04 100644
--- a/src/gn/ninja_build_writer.cc
+++ b/src/gn/ninja_build_writer.cc
@@ -19,6 +19,8 @@
 #include "gn/builder.h"
 #include "gn/err.h"
 #include "gn/escape.h"
+#include "gn/ffi/bridge.h"
+#include "gn/ffi/session.h"
 #include "gn/filesystem_utils.h"
 #include "gn/input_file_manager.h"
 #include "gn/loader.h"
@@ -352,33 +354,31 @@
   dep_out_ << "build.ninja.stamp:";
 
   // Other files read by the build.
-  std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies();
-
-  const InputFileManager* input_file_manager =
-      g_scheduler->input_file_manager();
-
-  VectorSetSorter<base::FilePath> sorter(
-      input_file_manager->GetInputFileCount() + other_files.size());
-
-  input_file_manager->AddAllPhysicalInputFileNamesToVectorSetSorter(&sorter);
-  sorter.Add(other_files.begin(), other_files.end());
+  std::vector<base::FilePath> files = g_scheduler->GetGenDependencies();
+  g_scheduler->input_file_manager()->AddAllPhysicalInputFiles(files);
 
   const base::FilePath build_path =
       build_settings_->build_dir().Resolve(build_settings_->root_path());
 
+  for (auto& file : files) {
+    file = MakeAbsoluteFilePathRelativeIfPossible(build_path, file)
+               .NormalizePathSeparatorsTo('/');
+  }
+
+  if (build_settings_->has_starlark_session()) {
+    add_all_loaded_files(build_settings_->starlark_session(), files);
+  }
+
+  std::sort(files.begin(), files.end());
+  files.erase(std::unique(files.begin(), files.end()), files.end());
+
   EscapeOptions depfile_escape;
   depfile_escape.mode = ESCAPE_DEPFILE;
-  auto item_callback = [this, &depfile_escape,
-                        &build_path](const base::FilePath& input_file) {
-    const base::FilePath file =
-        MakeAbsoluteFilePathRelativeIfPossible(build_path, input_file);
+  for (const auto& file : files) {
     dep_out_ << " ";
-    EscapeStringToStream(dep_out_,
-                         FilePathToUTF8(file.NormalizePathSeparatorsTo('/')),
-                         depfile_escape);
-  };
-
-  sorter.IterateOver(item_callback);
+    EscapeStringToStream(dep_out_, FilePathToUTF8(file), depfile_escape);
+  }
+  dep_out_ << "\n";
 
   out_ << std::endl;
 }
diff --git a/src/gn/ninja_build_writer_unittest.cc b/src/gn/ninja_build_writer_unittest.cc
index 493ba40..9625d4b 100644
--- a/src/gn/ninja_build_writer_unittest.cc
+++ b/src/gn/ninja_build_writer_unittest.cc
@@ -294,7 +294,7 @@
   ASSERT_TRUE(writer.Run(&err));
 
   EXPECT_EQ(depfile_out.str(),
-            "build.ninja.stamp: ../../path\\ with\\ space/BUILD.gn");
+            "build.ninja.stamp: ../../path\\ with\\ space/BUILD.gn\n");
 }
 
 TEST_F(NinjaBuildWriterTest, DuplicateOutputs) {
diff --git a/src/gn/starlark/crates/attr/src/allow_files.rs b/src/gn/starlark/crates/attr/src/allow_files.rs
index ad5e71e..1806c99 100644
--- a/src/gn/starlark/crates/attr/src/allow_files.rs
+++ b/src/gn/starlark/crates/attr/src/allow_files.rs
@@ -91,7 +91,7 @@
         } else {
             // It's a file.
             allow_files.validate(s)?;
-            LabelOrFile::File(path_resolver.source_file(relative_to, s)?)
+            LabelOrFile::File(path_resolver.existing_source_file(relative_to, s)?)
         },
     )
 }
diff --git a/src/gn/starlark/crates/attr/src/attr.rs b/src/gn/starlark/crates/attr/src/attr.rs
index 2e24128..375a1b5 100644
--- a/src/gn/starlark/crates/attr/src/attr.rs
+++ b/src/gn/starlark/crates/attr/src/attr.rs
@@ -385,7 +385,7 @@
             assert_eq!(
                 Attr::create(&schema, Some(heap.alloc("file.cc")), pkg, &path_resolver,).unwrap(),
                 Attr::Label(Some(LabelOrFile::File(
-                    path_resolver.source_file(pkg, "file.cc").unwrap()
+                    path_resolver.existing_source_file(pkg, "file.cc").unwrap()
                 )))
             );
 
diff --git a/src/gn/starlark/crates/ffi/src/bridge.rs b/src/gn/starlark/crates/ffi/src/bridge.rs
index 5cb17d2..29da1af 100644
--- a/src/gn/starlark/crates/ffi/src/bridge.rs
+++ b/src/gn/starlark/crates/ffi/src/bridge.rs
@@ -353,6 +353,8 @@
             err: Pin<&mut Err>,
         );
 
+        fn loaded(self: &Session) -> Vec<&'static str>;
+
         type OwnedFrozenValue;
         #[rust_name = "clone_cxx"]
         fn clone(self: &OwnedFrozenValue) -> Box<OwnedFrozenValue>;
diff --git a/src/gn/starlark/crates/ffi/src/session.rs b/src/gn/starlark/crates/ffi/src/session.rs
index 8c7c0c6..3c04e99 100644
--- a/src/gn/starlark/crates/ffi/src/session.rs
+++ b/src/gn/starlark/crates/ffi/src/session.rs
@@ -54,10 +54,11 @@
 impl Session {
     /// Creates a new `Session`.
     pub fn from_resolver(path_resolver: PathResolver) -> Self {
-        let loader = FileLoader::default();
-        loader.preload(rule::register_builtin_rules::<
-            crate::eval_context::EvalContext,
-        >());
+        let mut loader = FileLoader::default();
+        loader.preload(
+            Label::parse("//builtins:rules.scl", PackageRef::root()).unwrap(),
+            rule::register_builtin_rules::<crate::eval_context::EvalContext>(),
+        );
         Self {
             loader,
             path_resolver,
@@ -80,6 +81,18 @@
         Box::new(Self::from_resolver(PathResolver::new_for_testing()))
     }
 
+    pub fn loaded(&self) -> Vec<&'static str> {
+        self.loader
+            .loaded()
+            .into_iter()
+            .map(|label| {
+                self.path_resolver
+                    .source_file(label.package(), label.name())
+                    .as_str()
+            })
+            .collect()
+    }
+
     pub(crate) fn register_target(&self, target: Target) -> crate::TargetRef {
         let label = target.label().as_ref().to_owned();
         let toolchain = target.toolchain().to_owned();
diff --git a/src/gn/starlark/crates/loader/src/loader.rs b/src/gn/starlark/crates/loader/src/loader.rs
index 26ef7ef..1381d69 100644
--- a/src/gn/starlark/crates/loader/src/loader.rs
+++ b/src/gn/starlark/crates/loader/src/loader.rs
@@ -16,7 +16,7 @@
     syntax::{AstModule, Dialect},
     values::FrozenHeapName,
 };
-use types::{EvalContext, EvaluatorContextExt as _, LabelRef, PackageRef, PathResolver};
+use types::{EvalContext, EvaluatorContextExt as _, Label, LabelRef, PackageRef, PathResolver};
 
 use crate::Error;
 
@@ -33,7 +33,7 @@
         wait: Arc<Condvar>,
         // What needs to finish evaluating before this can be evaluated.
         // This is used purely for cycle detection.
-        needs: Option<String>,
+        needs: Option<Label>,
     },
     Loaded(starlark::Result<Pin<Box<FrozenModule>>>),
 }
@@ -51,17 +51,19 @@
 /// (`.bzl` files).
 #[derive(Default)]
 pub struct FileLoader {
-    files: RwLock<HashMap<String, Arc<Mutex<FileStatus>>>>,
+    files: RwLock<HashMap<Label, Arc<Mutex<FileStatus>>>>,
+    preloaded: Vec<Label>,
 }
 
 impl FileLoader {
     /// Preloads a frozen module into the loader's cache.
     /// This should be called once for each builtin module.
     /// By convention, builtin modules will be named "//builtins:$NAME.scl"
-    pub fn preload(&self, module: FrozenModule) {
+    pub fn preload(&mut self, label: Label, module: FrozenModule) {
         let mut files = self.files.write().unwrap();
+        self.preloaded.push(label.clone());
         files.insert(
-            module.frozen_heap().name().unwrap().to_string(),
+            label,
             Arc::new(Mutex::new(FileStatus::Loaded(Ok(Box::pin(module))))),
         );
     }
@@ -114,11 +116,9 @@
         globals: &Globals,
         make_eval_context: &F,
     ) -> starlark::Result<FrozenModule> {
-        // Starlark-rs requires module identifiers to be strings.
-        let label_str = label.to_string();
         let file_status = {
             let mut loader = self.files.write().unwrap();
-            match loader.entry(label_str.clone()) {
+            match loader.entry(label.to_owned()) {
                 Entry::Occupied(entry) => {
                     let file_status = entry.get().clone();
                     drop(loader);
@@ -131,7 +131,6 @@
 
         let result = self.load_and_evaluate(
             label,
-            &label_str,
             &file_status,
             path_resolver,
             globals,
@@ -144,7 +143,6 @@
     fn load_and_evaluate<'b, C: EvalContext, F: Fn(&PackageRef) -> C>(
         &self,
         label: LabelRef<'b>,
-        label_str: &str,
         file_status: &Arc<Mutex<FileStatus>>,
         path_resolver: &PathResolver,
         globals: &Globals,
@@ -152,17 +150,19 @@
     ) -> starlark::Result<FrozenModule> {
         // Read and parse the file to get its dependencies.
         let absolute_path = path_resolver.absolute_path(label.package(), label.name());
-        let content = fs::read_to_string(&absolute_path)
-            .map_err(|_| Error::ReadFailed(label_str.to_owned()))?;
-        let ast = AstModule::parse(label_str, content, &BZL_FILE_DIALECT)?;
+        let content =
+            fs::read_to_string(&absolute_path).map_err(|_| Error::ReadFailed(label.to_string()))?;
+        let ast = AstModule::parse(&label.to_string(), content, &BZL_FILE_DIALECT)?;
 
         let mut deps: Vec<(String, FrozenModule)> = Default::default();
         if !ast.loads().is_empty() {
             for load in ast.loads() {
                 let dep_label = types::Label::parse(load.module_id, label.package())?;
-                let dep_label_str = dep_label.to_string();
-                if let Some(cycle) = self.find_cycle_path(file_status, &dep_label_str) {
-                    return Err(Error::CycleDetected(cycle).into());
+                if let Some(cycle) = self.find_cycle_path(file_status, &dep_label) {
+                    return Err(Error::CycleDetected(
+                        cycle.into_iter().map(|l| l.to_string()).collect(),
+                    )
+                    .into());
                 }
 
                 deps.push((
@@ -189,15 +189,15 @@
                 eval.set_loader(&loader);
                 eval.eval_module(ast, globals)?;
             }
-            Ok(module.freeze_named(FrozenHeapName::user(label_str.to_owned()))?)
+            Ok(module.freeze_named(FrozenHeapName::user(label.to_string()))?)
         })
     }
 
     fn find_cycle_path(
         &self,
         current: &Arc<Mutex<FileStatus>>,
-        target: &str,
-    ) -> Option<Vec<String>> {
+        target: &Label,
+    ) -> Option<Vec<Label>> {
         // Set the dependency before we start doing cycle detection.
         // This prevents multiple threads simultaneously calling find_cycle_path
         // not seeing a cycle on each other, then adding a dependency on each
@@ -209,10 +209,10 @@
                 // currently trying to load.
                 unreachable!();
             };
-            *needs = Some(target.to_owned());
+            *needs = Some(target.clone());
         }
         let loader = self.files.read().unwrap();
-        let mut cur = target.to_owned();
+        let mut cur = target.clone();
         let mut cycle = vec![cur.clone()];
         while let Some(status_mutex) = loader.get(&cur) {
             let status = status_mutex.lock().unwrap();
@@ -231,6 +231,16 @@
         }
         None
     }
+
+    /// Returns a list of all labels explicitly loaded by the loader
+    pub fn loaded(&self) -> Vec<Label> {
+        let files = self.files.read().unwrap();
+        files
+            .keys()
+            .filter(|k| !self.preloaded.contains(k))
+            .cloned()
+            .collect()
+    }
 }
 
 /// Helper loader to load preloaded dependencies during evaluator execution.
@@ -253,7 +263,6 @@
     use std::rc::Rc;
 
     use testutils::{FakeEvalContext, FakeSession};
-    use types::Label;
 
     use super::*;
 
diff --git a/src/gn/starlark/crates/types/src/path_resolver.rs b/src/gn/starlark/crates/types/src/path_resolver.rs
index d1eef0b..9cdf05d 100644
--- a/src/gn/starlark/crates/types/src/path_resolver.rs
+++ b/src/gn/starlark/crates/types/src/path_resolver.rs
@@ -55,23 +55,28 @@
     }
 
     /// Creates a `File` object for a file path relative to a package.
-    /// Validates that the file exists on disk.
-    pub fn source_file(&self, pkg: &PackageRef, s: &str) -> starlark::Result<File> {
-        // Gn *does not* check that the files you refer to exist on disk.
-        // This is because native GN allows referencing generated files that do
-        // not yet exist at `gn gen` time.
-        // We explicitly disallow referencing generated files in starlark, so
-        // we should validate that the files exist on disk for correctness.
-        let abs_path = self.absolute_path(pkg, s);
-        if !abs_path.exists() {
-            return Err(crate::Error::FileNotFound(pkg.to_owned(), s.to_owned()).into());
-        }
+    pub fn source_file(&self, pkg: &PackageRef, s: &str) -> File {
         let pkg_dir = pkg.as_source_relative();
         let rel_path = if pkg.is_root() {
             format!("{}{s}", self.source_root_rel)
         } else {
             format!("{}{pkg_dir}/{s}", self.source_root_rel)
         };
-        Ok(File::intern(&rel_path))
+        File::intern(&rel_path)
+    }
+
+    /// Creates a `File` object for a file path relative to a package.
+    /// Validates that the file exists on disk.
+    pub fn existing_source_file(&self, pkg: &PackageRef, s: &str) -> starlark::Result<File> {
+        // Gn *does not* check that the files you refer to exist on disk.
+        // This is because native GN allows referencing generated files that do
+        // not yet exist at `gn gen` time.
+        // We explicitly disallow referencing generated files in starlark, so
+        // we should validate that the files exist on disk for correctness.
+        if self.absolute_path(pkg, s).exists() {
+            Ok(self.source_file(pkg, s))
+        } else {
+            Err(crate::Error::FileNotFound(pkg.to_owned(), s.to_owned()).into())
+        }
     }
 }