Module: Everywhere::Paths

Defined in:
lib/everywhere/paths.rb

Overview

Filesystem locations the CLI resolves relative to the gem itself, so every dev/build/release work in any project the gem is installed into.

Class Method Summary collapse

Class Method Details

.android_dirObject

Absolute path to the Android template, or nil when it isn't there. Unlike #ios_dir, nil is the NORMAL state until the template lands — callers must read it as "Android isn't buildable in this release yet", not as a corrupt install. settings.gradle.kts is the marker because it's the one file every Gradle project must have at its root.



77
78
79
# File 'lib/everywhere/paths.rb', line 77

def android_dir
  bundled_android_dir if File.exist?(File.join(bundled_android_dir, "settings.gradle.kts"))
end

.android_dir!Object

Like #android_dir but aborts with a helpful message when the template is missing. The --template-dir hint covers dev on a modified template.



83
84
85
86
# File 'lib/everywhere/paths.rb', line 83

def android_dir!
  android_dir or UI.die!("couldn't find the bundled Android template at #{bundled_android_dir}; " \
                         "reinstall ruby_everywhere or pass --template-dir")
end

.android_fonts_dirObject

Where the Material Symbols icon font is cached after its one-time fetch. The font is ~10 MB, so it's fetched on first Android build instead of riding along in a 229 KB gem that most users never build Android with.



136
137
138
# File 'lib/everywhere/paths.rb', line 136

def android_fonts_dir
  File.join(cache_dir, "android-fonts")
end

.android_gradle_homeObject

GRADLE_USER_HOME for shell builds. Redirected here so the multi-GB Gradle distribution, the Android Gradle Plugin and the Hotwire artifacts are downloaded once and stay warm across every app — and never land inside the installed gem. Same trick as cargo_target_dir.



129
130
131
# File 'lib/everywhere/paths.rb', line 129

def android_gradle_home
  File.join(cache_dir, "android-gradle")
end

.android_work_dir(application_id) ⇒ Object

The stamped copy of the Android template for one app, keyed by applicationId. Persistent for the same reasons ios_work_dir is: Gradle's incremental state is path-sensitive, and the stamped project opens in Android Studio unmodified.



121
122
123
# File 'lib/everywhere/paths.rb', line 121

def android_work_dir(application_id)
  File.join(cache_dir, "android", segment!(application_id))
end

.app_bundle_dirObject

Where a build runner installs the APP's gems (BUNDLE_PATH), so CI can cache them across builds of the same app. Local builds never use this — dev machines keep their own bundler setup; the runner opts in by setting EVERY_BUNDLE_PATH (see Commands::Build#bundle_env).



200
201
202
# File 'lib/everywhere/paths.rb', line 200

def app_bundle_dir
  File.join(cache_dir, "bundle")
end

.bundled_android_dirObject

The Hotwire Native Android shell template bundled inside the gem: support/mobile/android/ holds a frozen Gradle project (wrapper committed) the CLI stamps per-app — everywhere.properties + assets + res, never a build script. Same freeze contract the iOS template has.



68
69
70
# File 'lib/everywhere/paths.rb', line 68

def bundled_android_dir
  File.join(gem_root, "support", "mobile", "android")
end

.bundled_desktop_dirObject

The generic Tauri shell bundled inside the gem: support/desktop/ holds the src-tauri app and the splash/ dir it serves, alongside support/mobile/ios and support/mobile/android. This is the CANONICAL copy of the shell — external consumers (e.g. the build-runner repo) resolve it via every shell-dir. The CLI uses it unless the caller passes --shell-dir.

The directory is named for the platform; the reader keeps the "shell" wording because that's what the flag, the command and every caller call it.



26
27
28
# File 'lib/everywhere/paths.rb', line 26

def bundled_desktop_dir
  File.join(gem_root, "support", "desktop", "src-tauri")
end

.bundled_ios_dirObject

The Hotwire Native iOS shell template bundled inside the gem: support/mobile/ios/ holds a frozen Xcode project the CLI stamps per-app (xcconfig + everywhere.json + AppIcon — never the pbxproj). The mobile/ parent leaves room for support/mobile/android later.



47
48
49
# File 'lib/everywhere/paths.rb', line 47

def bundled_ios_dir
  File.join(gem_root, "support", "mobile", "ios")
end

.cache_dirObject

Per-user cache dir. Keeps heavy build state out of the gem and the app. ~ needs a home to expand against, and a container run with --user has neither HOME nor a passwd entry.



91
92
93
94
95
# File 'lib/everywhere/paths.rb', line 91

def cache_dir
  File.expand_path(ENV["RUBYEVERYWHERE_HOME"] || "~/.rubyeverywhere")
rescue ArgumentError
  UI.die!("couldn't resolve your home directory — set HOME or RUBYEVERYWHERE_HOME")
end

.cargo_target_dirObject

Where cargo writes the shell's build output (CARGO_TARGET_DIR) for apps on the fast path — the ones that declare no native.desktop. Redirected here so cargo never dumps a multi-GB target/ into the installed gem, and so the shell compiles warm across projects: with no app Rust in it the shell source really is identical for everyone, so one shared target dir is safe to reuse. An app that declares extensions gets desktop_target_dir instead, because that premise stops holding the moment its crate graph differs.



192
193
194
# File 'lib/everywhere/paths.rb', line 192

def cargo_target_dir
  File.join(cache_dir, "shell-target")
end

.desktop_assets_dir(bundle_id) ⇒ Object

Where every dev stages native/desktop/assets for the running shell. A staged copy rather than the source tree on purpose: the packaged app reads ONE flat Resources/assets folder, and pointing dev at the nested source would quietly resolve assets/branding/logo.png in a build and not in dev. Staging runs the same DesktopAssets compiler both ways, so the two can't drift. Keyed by bundle id like the mobile work dirs.



181
182
183
# File 'lib/everywhere/paths.rb', line 181

def desktop_assets_dir(bundle_id)
  File.join(cache_dir, "desktop-assets", segment!(bundle_id))
end

.desktop_dev_app_dir(bundle_id) ⇒ Object

Where every dev keeps the throwaway .app wrapper it runs the shell inside. macOS reads the application name from CFBundleName, so a bare cargo run binary shows up as the crate name — "example-shell" — in the menu bar, the About/Hide/Quit items and the Dock. The wrapper exists only to carry an Info.plist; the binary inside it is a hard link to cargo's.



164
165
166
# File 'lib/everywhere/paths.rb', line 164

def desktop_dev_app_dir(bundle_id)
  File.join(cache_dir, "desktop-app", segment!(bundle_id))
end

.desktop_icon_dir(bundle_id) ⇒ Object

Where every dev stages the app's Dock icon for the running shell. The shaped master is derived from app.icon, so it's a build product, not the app's own file — it belongs in the cache next to the rest of them.



171
172
173
# File 'lib/everywhere/paths.rb', line 171

def desktop_icon_dir(bundle_id)
  File.join(cache_dir, "desktop-icon", segment!(bundle_id))
end

.desktop_target_dir(bundle_id) ⇒ Object

CARGO_TARGET_DIR for a stamped shell. Per-app rather than shared, because the whole point of stamping is that this app's crate graph differs from every other app's — pointing them at one target dir would make cargo rebuild the world on every alternating build.



155
156
157
# File 'lib/everywhere/paths.rb', line 155

def desktop_target_dir(bundle_id)
  File.join(cache_dir, "desktop-target", segment!(bundle_id))
end

.desktop_work_dir(bundle_id) ⇒ Object

The staged copy of the Tauri shell for one app, keyed by bundle id. Every app builds from one of these — cargo never runs inside the installed gem, which may be root-owned — but only apps that declare native.desktop get their Rust stamped in; everyone else's copy is identical to the template and shares cargo_target_dir so the compile stays warm across projects. Persistent for the same reason the mobile work dirs are: a cold Tauri build is minutes where a warm one is seconds.



147
148
149
# File 'lib/everywhere/paths.rb', line 147

def desktop_work_dir(bundle_id)
  File.join(cache_dir, "desktop", segment!(bundle_id))
end

.gem_rootObject

The gem's own root — the dir holding lib/, exe/, support/. Correct whether the code runs from a git checkout or an installed gem.



14
15
16
# File 'lib/everywhere/paths.rb', line 14

def gem_root
  File.expand_path("../..", __dir__)
end

.ios_derived_data_dirObject

Where xcodebuild writes DerivedData for iOS shell builds. Shared across apps like cargo_target_dir — safe because the product is always App.app; worst case a mismatch is a cache miss, never a wrong artifact.



107
108
109
# File 'lib/everywhere/paths.rb', line 107

def ios_derived_data_dir
  File.join(cache_dir, "ios-derived-data")
end

.ios_dirObject

Absolute path to the iOS template, or nil if the bundled copy is missing (a corrupt/partial install).



53
54
55
# File 'lib/everywhere/paths.rb', line 53

def ios_dir
  bundled_ios_dir if File.exist?(File.join(bundled_ios_dir, "App.xcodeproj", "project.pbxproj"))
end

.ios_dir!Object

Like #ios_dir but aborts with a helpful message when the template is missing. The --template-dir hint covers dev on a modified template.



59
60
61
62
# File 'lib/everywhere/paths.rb', line 59

def ios_dir!
  ios_dir or UI.die!("couldn't find the bundled iOS template at #{bundled_ios_dir}; " \
                     "reinstall ruby_everywhere or pass --template-dir")
end

.ios_packages_dirObject

Where SPM clones the shell's package dependencies (hotwire-native-ios), so resolution is warm/offline after the first build.



113
114
115
# File 'lib/everywhere/paths.rb', line 113

def ios_packages_dir
  File.join(cache_dir, "ios-packages")
end

.ios_work_dir(bundle_id) ⇒ Object

The stamped copy of the iOS template for one app. Persistent (not a tmpdir) on purpose: a stable project path keeps Xcode's incremental state valid across builds, and users can open the stamped project in Xcode.



100
101
102
# File 'lib/everywhere/paths.rb', line 100

def ios_work_dir(bundle_id)
  File.join(cache_dir, "ios", segment!(bundle_id))
end

.segment!(value) ⇒ Object

Bundle ids and application ids come from the app's everywhere.yml and are used as ONE path segment under the cache — directories the builders rm_rf wholesale. Config#identity_errors rejects a malformed id before a build starts; this is the backstop for every other caller. Raise, not die!: Paths is reachable from the packaged app's runtime.



209
210
211
212
213
214
215
216
217
# File 'lib/everywhere/paths.rb', line 209

def segment!(value)
  segment = value.to_s
  if segment.empty? || segment.include?("/") || segment.include?("\\") || segment.include?("..")
    raise Everywhere::Error, "#{segment.inspect} can't be a directory name (app.bundle_id in " \
                             "config/everywhere.yml must be reverse-DNS, like com.example.app)"
  end

  segment
end

.shell_dirObject

Absolute path to the shell's src-tauri directory, or nil if the bundled copy is missing (a corrupt/partial install).



32
33
34
# File 'lib/everywhere/paths.rb', line 32

def shell_dir
  bundled_desktop_dir if File.exist?(File.join(bundled_desktop_dir, "tauri.conf.json"))
end

.shell_dir!Object

Like #shell_dir but aborts with a helpful message when the shell is missing. The --shell-dir hint covers dev on a modified shell checkout.



38
39
40
41
# File 'lib/everywhere/paths.rb', line 38

def shell_dir!
  shell_dir or UI.die!("couldn't find the bundled shell at #{bundled_desktop_dir}; " \
                       "reinstall ruby_everywhere or pass --shell-dir")
end