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
37 changes: 5 additions & 32 deletions lib/dev/deps/gh_integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
require "digest"
require "fileutils"
require "pathname"
require "securerandom"
require "shellwords"
require_relative "../data_root"
require_relative "integration"
Expand Down Expand Up @@ -39,6 +38,11 @@ module Deps
# multi-gigabyte (the UE engine is ~8GB compressed; a built tree far more), so
# parking a second copy in ~/.dev/cache would double disk usage for no benefit.
# The version-keyed install dir plus its marker file is the cache.
#
# Nothing else is written under install_dir: consumers locate the version
# dir through `dev deps path gh <name>` (Accessor), so an install is a pure
# read once the marker matches — a read-only shared tree (the agent identity
# on the human-owned engine tree, plans#26) needs no write to stay usable.
class GhIntegration < Integration
extend T::Sig

Expand Down Expand Up @@ -100,7 +104,6 @@ def install_prebuilt(dep)
target_dir = versioned_dir(base_dir, dep.version)
if version_published?(target_dir, MARKER_FILE, dep.version)
puts ">>> #{dep.name}@#{dep.version} already installed at #{target_dir}"
publish_current(base_dir, target_dir)
return
end

Expand All @@ -127,7 +130,6 @@ def install_prebuilt(dep)
else
puts ">>> #{dep.name}@#{dep.version} published concurrently at #{target_dir}"
end
publish_current(base_dir, target_dir)
ensure
FileUtils.rm_rf(staging_dir) if staging_dir
end
Expand All @@ -139,7 +141,6 @@ def install_from_source(dep)
target_dir = versioned_dir(base_dir, dep.version)
if version_published?(target_dir, MARKER_FILE, dep.version)
puts ">>> #{dep.name}@#{dep.version} already installed at #{target_dir}"
publish_current(base_dir, target_dir)
return
end

Expand All @@ -164,38 +165,10 @@ def install_from_source(dep)
else
puts ">>> #{dep.name}@#{dep.version} published concurrently at #{target_dir}"
end
publish_current(base_dir, target_dir)
ensure
FileUtils.rm_rf(staging_dir) if staging_dir
end

# Point <install_dir>/current at the just-installed version via a relative
# symlink, swapped in atomically. Host consumers (e.g. cellbound's
# build-game.sh via UE_ROOT) reference this stable path without knowing the
# locked tag; the versioned dirs themselves stay immutable — only this
# pointer moves, to the most recently installed version.
#
# @param base_dir [Pathname] declared install_dir
# @param target_dir [Pathname] the published version dir
sig { params(base_dir: Pathname, target_dir: Pathname).void }
def publish_current(base_dir, target_dir)
link = base_dir / "current"
desired = target_dir.basename.to_s
# A pointer that already resolves to the target is converged — skip
# the rewrite. The swap is not free idempotence but a write, and
# identities that can only read the shared tree (the ai-agent user
# on the human-owned engine tree, plans#26) crash on it (caught
# live at the plans#36 ceremony via dev install-deps).
return if File.symlink?(link.to_s) && File.readlink(link.to_s) == desired

tmp = base_dir / ".current-#{Process.pid}-#{SecureRandom.hex(4)}"
File.symlink(desired, tmp.to_s)
File.rename(tmp.to_s, link.to_s)
rescue StandardError
FileUtils.rm_f(tmp.to_s) if tmp
raise
end

# Fetch the tag's source tarball into archive_path. Uses `gh api .../tarball`
# rather than a bare codeload URL so the request carries gh's auth token and
# follows the redirect — required for private/Epic-gated repos. Isolated so
Expand Down
17 changes: 8 additions & 9 deletions test/dev/deps/gh_integration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def build_integration(fixture_files, cache_dir)
File.read(File.join(version_dir, "Engine", "engine.txt")) == "engine payload"
File.read(File.join(version_dir, "README.md")) == "readme payload"
File.read(File.join(version_dir, ".dev-gh-release")) == "5.6.1-css-83"
File.readlink(File.join(install_dir, "current")) == "5.6.1-css-83"
!File.exist?(File.join(install_dir, "current"))
Dir.glob(File.join(install_dir, ".staging-*")).empty?
integration.download_count == 1

Expand Down Expand Up @@ -170,26 +170,25 @@ def build_integration(fixture_files, cache_dir)
FileUtils.rm_rf(dir)
end

test "install_all leaves an already-correct current pointer untouched — a read-only shared tree stays usable" do
Given "a published version whose current pointer already resolves to it, in a base dir the " \
"caller cannot write (the agent identity on the human-owned shared engine tree, plans#26)"
test "install_all on an already-published version writes nothing — a read-only shared tree stays usable" do
Given "a published version in a base dir the caller cannot write (the agent identity on " \
"the human-owned shared engine tree, plans#26)"
dir = Dir.mktmpdir("dev-gh-int-test-")
parts = build_split_archive(dir, "engine.tar.zst", part_size: 64)
install_dir = File.join(dir, "engines", "unreal-engine-css")
version_dir = File.join(install_dir, "5.6.1-css-83")
FileUtils.mkdir_p(version_dir)
File.write(File.join(version_dir, ".dev-gh-release"), "5.6.1-css-83")
File.symlink("5.6.1-css-83", File.join(install_dir, "current"))
FileUtils.chmod(0o555, install_dir)
dep = build_dependency(parts, install_dir)
integration = build_integration(parts, File.join(dir, "cache"))

When "installing again"
integration.install_all([dep])

Then "the converged pointer was recognized, not rewritten (caught live at the plans#36 " \
"ceremony: the unconditional rewrite crashed dev install-deps with EACCES)"
File.readlink(File.join(install_dir, "current")) == "5.6.1-css-83"
Then "the marker was recognized and no write was attempted (the retired current-pointer " \
"rewrite crashed dev install-deps here with EACCES at the plans#36 ceremony)"
!File.exist?(File.join(install_dir, "current"))
integration.download_count == 0

Cleanup
Expand Down Expand Up @@ -351,7 +350,7 @@ def build_source_integration(tarball, project_root, cache_dir)
File.read(File.join(version_dir, "built.txt")) == "engine source"
File.read(File.join(version_dir, "version.txt")).strip == "5.6.1-release"
File.read(File.join(version_dir, ".dev-gh-release")) == "5.6.1-release"
File.readlink(File.join(install_dir, "current")) == "5.6.1-release"
!File.exist?(File.join(install_dir, "current"))
Dir.glob(File.join(install_dir, ".staging-*")).empty?
integration.download_count == 1

Expand Down
Loading