ruby_everywhere 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/exe/every +12 -1
- data/exe/rbe +12 -1
- data/lib/everywhere/agents_guide.rb +3 -1
- data/lib/everywhere/blake2b.rb +17 -1
- data/lib/everywhere/boot.rb +21 -4
- data/lib/everywhere/builders/android.rb +144 -30
- data/lib/everywhere/builders/desktop.rb +25 -17
- data/lib/everywhere/builders/ios.rb +244 -9
- data/lib/everywhere/cli.rb +2 -0
- data/lib/everywhere/commands/build.rb +113 -58
- data/lib/everywhere/commands/clean.rb +8 -3
- data/lib/everywhere/commands/dev.rb +85 -11
- data/lib/everywhere/commands/doctor.rb +138 -21
- data/lib/everywhere/commands/install.rb +48 -8
- data/lib/everywhere/commands/platform/build.rb +136 -27
- data/lib/everywhere/commands/platform/login.rb +16 -2
- data/lib/everywhere/commands/platform/runner.rb +113 -23
- data/lib/everywhere/commands/preview.rb +359 -0
- data/lib/everywhere/commands/publish.rb +20 -2
- data/lib/everywhere/commands/release.rb +145 -28
- data/lib/everywhere/commands/shell_dir.rb +2 -2
- data/lib/everywhere/config.rb +88 -3
- data/lib/everywhere/console.rb +2 -1
- data/lib/everywhere/engine.rb +24 -0
- data/lib/everywhere/framework.rb +21 -4
- data/lib/everywhere/ignore.rb +3 -1
- data/lib/everywhere/jump.rb +121 -0
- data/lib/everywhere/mobile_config_endpoint.rb +47 -0
- data/lib/everywhere/mobile_configs_controller.rb +46 -0
- data/lib/everywhere/paths.rb +13 -6
- data/lib/everywhere/platform/client.rb +27 -6
- data/lib/everywhere/platform/credentials.rb +18 -8
- data/lib/everywhere/platform/snapshot.rb +10 -0
- data/lib/everywhere/receipt.rb +55 -10
- data/lib/everywhere/shellout.rb +19 -0
- data/lib/everywhere/simulator.rb +12 -1
- data/lib/everywhere/version.rb +1 -1
- data/support/mobile/android/app/build.gradle.kts +29 -1
- data/support/mobile/ios/App/EverywhereConfig.swift +17 -5
- data/support/mobile/ios/App/SceneDelegate.swift +75 -8
- data/support/mobile/ios/App.xcodeproj/project.pbxproj +2 -2
- data/support/mobile/ios/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +0 -1
- data/support/mobile/ios/README.md +13 -6
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 83b4d5f0135bc80870fdacccaf5014ee478cbaa2bad08541871ab67a6485d3fc
|
|
4
|
+
data.tar.gz: dd86792cae35b098877012c42ebe0286ada230909a8588c9a4c933c7c8771137
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 97ad2a4b22e6255abcf6e68850ef9c2615ca725bfac46f62db9d7f2423314afdd882088589670cd609e3f9a8da854616b1b7af0015dd29e2bafff587664ff838
|
|
7
|
+
data.tar.gz: ec09e6586570025a8e02acb4d61f323f841201441341de7aa1cf3cfa621c74d331d1e48794e6e88e87990511e4a215233b990c86cfca11829aa52be0ea82b12a
|
data/exe/every
CHANGED
|
@@ -2,5 +2,16 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "everywhere/cli"
|
|
5
|
+
require "everywhere/ui"
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
begin
|
|
8
|
+
Dry::CLI.new(Everywhere::CLI).call
|
|
9
|
+
rescue Interrupt
|
|
10
|
+
# ^C is an answer, not a crash: no backtrace, and the shell's own convention
|
|
11
|
+
# for "killed by SIGINT".
|
|
12
|
+
exit 130
|
|
13
|
+
rescue Everywhere::Error => e
|
|
14
|
+
# Everywhere::Fatal is a SystemExit, not an Error, so a die! that already
|
|
15
|
+
# printed its own line passes straight through here.
|
|
16
|
+
Everywhere::UI.die!(e.message)
|
|
17
|
+
end
|
data/exe/rbe
CHANGED
|
@@ -2,5 +2,16 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "everywhere/cli"
|
|
5
|
+
require "everywhere/ui"
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
begin
|
|
8
|
+
Dry::CLI.new(Everywhere::CLI).call
|
|
9
|
+
rescue Interrupt
|
|
10
|
+
# ^C is an answer, not a crash: no backtrace, and the shell's own convention
|
|
11
|
+
# for "killed by SIGINT".
|
|
12
|
+
exit 130
|
|
13
|
+
rescue Everywhere::Error => e
|
|
14
|
+
# Everywhere::Fatal is a SystemExit, not an Error, so a die! that already
|
|
15
|
+
# printed its own line passes straight through here.
|
|
16
|
+
Everywhere::UI.die!(e.message)
|
|
17
|
+
end
|
|
@@ -51,7 +51,9 @@ module Everywhere
|
|
|
51
51
|
path = File.join(template_dir, *parts)
|
|
52
52
|
File.exist?(path) or UI.die!("couldn't find the bundled AGENTS.md template at #{path}; " \
|
|
53
53
|
"reinstall ruby_everywhere")
|
|
54
|
-
|
|
54
|
+
# The templates are UTF-8 prose; under LANG=C the gsub below would raise
|
|
55
|
+
# on them rather than substitute.
|
|
56
|
+
File.read(path, encoding: "UTF-8").scrub
|
|
55
57
|
end
|
|
56
58
|
end
|
|
57
59
|
end
|
data/lib/everywhere/blake2b.rb
CHANGED
|
@@ -68,7 +68,12 @@ module Everywhere
|
|
|
68
68
|
rescue StandardError
|
|
69
69
|
nil
|
|
70
70
|
end
|
|
71
|
-
|
|
71
|
+
# `digest` is one-shot (no incremental update), so the fallback has to hold
|
|
72
|
+
# the whole artifact in memory — worth warning about on a 300MB .zip.
|
|
73
|
+
unless md
|
|
74
|
+
warn_pure_ruby_fallback(path)
|
|
75
|
+
return digest(File.binread(path), 64)
|
|
76
|
+
end
|
|
72
77
|
|
|
73
78
|
File.open(path, "rb") do |io|
|
|
74
79
|
while (chunk = io.read(1 << 20))
|
|
@@ -78,6 +83,17 @@ module Everywhere
|
|
|
78
83
|
md.digest
|
|
79
84
|
end
|
|
80
85
|
|
|
86
|
+
# Once per process — signing prehashes the same artifact more than once.
|
|
87
|
+
def warn_pure_ruby_fallback(path)
|
|
88
|
+
return if @warned_fallback
|
|
89
|
+
|
|
90
|
+
@warned_fallback = true
|
|
91
|
+
require_relative "ui"
|
|
92
|
+
mb = (File.size(path) / 1024.0 / 1024.0).round(1)
|
|
93
|
+
Everywhere::UI.warn("this Ruby's OpenSSL has no BLAKE2b512 — hashing #{mb}MB in pure Ruby " \
|
|
94
|
+
"(slow, and the file is read into memory)")
|
|
95
|
+
end
|
|
96
|
+
|
|
81
97
|
def compress(h, block, t, last)
|
|
82
98
|
m = block.unpack("Q<16")
|
|
83
99
|
v = h + IV
|
data/lib/everywhere/boot.rb
CHANGED
|
@@ -64,15 +64,32 @@ module Everywhere
|
|
|
64
64
|
def default_app_data
|
|
65
65
|
case RUBY_PLATFORM
|
|
66
66
|
when /darwin/
|
|
67
|
-
File.join(
|
|
67
|
+
File.join(home, "Library", "Application Support", config.bundle_id)
|
|
68
68
|
when /mingw|mswin/
|
|
69
|
-
File.join(
|
|
69
|
+
File.join(app_data_env("APPDATA"), config.bundle_id)
|
|
70
70
|
else
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
# `||`, not ENV.fetch's default: the fallback resolves a home that a
|
|
72
|
+
# machine setting XDG_DATA_HOME needn't have.
|
|
73
|
+
File.join(ENV["XDG_DATA_HOME"] || File.join(home, ".local", "share"), config.bundle_id)
|
|
73
74
|
end
|
|
74
75
|
end
|
|
75
76
|
|
|
77
|
+
# Only reached in a standalone run — the shell always passes NATIVE_APPDATA.
|
|
78
|
+
# Dir.home raises when HOME is unset and there's no passwd entry (a
|
|
79
|
+
# container run with --user), which is a bare ArgumentError otherwise.
|
|
80
|
+
def home
|
|
81
|
+
Dir.home
|
|
82
|
+
rescue ArgumentError
|
|
83
|
+
raise Error, app_data_hint("no home directory")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def app_data_env(key)
|
|
87
|
+
ENV[key] or raise Error, app_data_hint("#{key} is unset")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def app_data_hint(reason) = "can't work out where to keep app data (#{reason}) — " \
|
|
91
|
+
"set NATIVE_APPDATA (or HOME / APPDATA)"
|
|
92
|
+
|
|
76
93
|
def prepare_app_data
|
|
77
94
|
storage = File.join(app_data, "storage")
|
|
78
95
|
FileUtils.mkdir_p(storage)
|
|
@@ -32,13 +32,34 @@ module Everywhere
|
|
|
32
32
|
# app/src/stamped/res/drawable*/ native/android/assets/** as drawables
|
|
33
33
|
# app/src/main/java/com/rubyeverywhere/shell/extensions/ native/android/*.kt + registry
|
|
34
34
|
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
35
|
+
# Signing and the format Play accepts are driven from the environment, not
|
|
36
|
+
# from everywhere.yml — see SIGNING_ENV.
|
|
37
37
|
class Android
|
|
38
38
|
# Fixed by the template contract: one Gradle module, always :app, so
|
|
39
39
|
# build paths and task names never depend on per-app values.
|
|
40
40
|
GRADLE_MODULE = ":app"
|
|
41
41
|
|
|
42
|
+
# What each package format is called in the three places it has a name:
|
|
43
|
+
# the Gradle verb, the outputs/ subdirectory and the file extension.
|
|
44
|
+
FORMATS = {
|
|
45
|
+
apk: { task: "assemble", dir: "apk", extension: "apk" },
|
|
46
|
+
aab: { task: "bundle", dir: "bundle", extension: "aab" }
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
# Release signing is read from the environment and handed to Gradle as
|
|
50
|
+
# environment, never stamped into everywhere.properties: that file lives
|
|
51
|
+
# in the work dir for the life of the app, and a keystore password written
|
|
52
|
+
# there outlives the build that needed it. The keystore itself stays where
|
|
53
|
+
# the machine put it — nothing is copied into the project.
|
|
54
|
+
SIGNING_ENV = %w[EVERY_ANDROID_KEYSTORE EVERY_ANDROID_KEYSTORE_PASSWORD
|
|
55
|
+
EVERY_ANDROID_KEY_ALIAS EVERY_ANDROID_KEY_PASSWORD].freeze
|
|
56
|
+
|
|
57
|
+
# The two the keystore can't be used without. EVERY_ANDROID_KEY_PASSWORD
|
|
58
|
+
# is not among them: a key generated without -keypass shares the store's.
|
|
59
|
+
REQUIRED_SIGNING_ENV = %w[EVERY_ANDROID_KEYSTORE_PASSWORD EVERY_ANDROID_KEY_ALIAS].freeze
|
|
60
|
+
|
|
61
|
+
VERSION_CODE_ENV = "EVERY_ANDROID_VERSION_CODE"
|
|
62
|
+
|
|
42
63
|
# The Kotlin package the shell's sources live in. NOT the applicationId:
|
|
43
64
|
# the app id is stamped per app, the code's package is frozen, and mixing
|
|
44
65
|
# them up is the classic Android manifest mistake — so anything we
|
|
@@ -103,24 +124,25 @@ module Everywhere
|
|
|
103
124
|
@template_dir = template_dir
|
|
104
125
|
end
|
|
105
126
|
|
|
106
|
-
# Build the
|
|
127
|
+
# Build the app and copy it into dist_dir. `dev` builds the debug variant
|
|
107
128
|
# and relaxes the remote-mode requirement (the dev URL is stamped into a
|
|
108
129
|
# debug asset each run — Android has no SIMCTL_CHILD_* equivalent).
|
|
109
|
-
#
|
|
110
|
-
|
|
130
|
+
# `format` is :apk (installable) or :aab (what Play accepts). Returns the
|
|
131
|
+
# path of the collected artifact.
|
|
132
|
+
def build!(dist_dir:, dev: false, dev_url: nil, format: :apk)
|
|
111
133
|
# The template is resolved before preflight, not after it like iOS:
|
|
112
134
|
# preflight has to settle the icon font, and the bundled font lives in
|
|
113
135
|
# the template. Everything that can fail slowly (a font download) or
|
|
114
136
|
# cheaply (a typo'd icon name) is therefore decided before Gradle — the
|
|
115
137
|
# difference between a 5-second error and one that lands 90 seconds in.
|
|
116
138
|
template = @template_dir ? File.expand_path(@template_dir) : Paths.android_dir!
|
|
117
|
-
preflight!(dev: dev, template: template)
|
|
139
|
+
preflight!(dev: dev, format: format, template: template)
|
|
118
140
|
work = stage(template)
|
|
119
141
|
stamp!(work)
|
|
120
142
|
stamp_dev_url!(work, dev_url)
|
|
121
143
|
variant = dev ? "debug" : "release"
|
|
122
|
-
gradle!(work, variant, dist_dir)
|
|
123
|
-
collect(work, variant, dist_dir)
|
|
144
|
+
gradle!(work, variant, format, dist_dir)
|
|
145
|
+
collect(work, variant, format, dist_dir)
|
|
124
146
|
end
|
|
125
147
|
|
|
126
148
|
private
|
|
@@ -149,13 +171,17 @@ module Everywhere
|
|
|
149
171
|
UI.step("pointing the shell at #{UI.cyan(dev_url)}")
|
|
150
172
|
end
|
|
151
173
|
|
|
152
|
-
def preflight!(dev:, template:)
|
|
174
|
+
def preflight!(dev:, format:, template:)
|
|
153
175
|
# Config shape first, machine state second: a typo in everywhere.yml is
|
|
154
176
|
# the app author's to fix wherever they are, while "no Android SDK"
|
|
155
177
|
# depends on the machine they happen to be sitting at.
|
|
156
178
|
errors = @config.native_android_errors
|
|
157
179
|
UI.die!("native.android in config/everywhere.yml:\n#{errors.join("\n")}") unless errors.empty?
|
|
158
180
|
|
|
181
|
+
check_format!(dev: dev, format: format)
|
|
182
|
+
@signing = dev ? nil : signing_env!
|
|
183
|
+
@version_code = version_code
|
|
184
|
+
|
|
159
185
|
unless dev || @config.remote?
|
|
160
186
|
UI.die!("Android apps are remote mode only for now — set remote.url in config/everywhere.yml " \
|
|
161
187
|
"(the shell wraps your deployed app; nothing is packaged locally)")
|
|
@@ -179,7 +205,10 @@ module Everywhere
|
|
|
179
205
|
# fix — the analogue of the iOS xcode-select message.
|
|
180
206
|
@sdk_root = AndroidSdk.sdk_root!
|
|
181
207
|
@java_home = AndroidSdk.java_home!
|
|
182
|
-
|
|
208
|
+
# adb only matters when something is going to be installed on a device,
|
|
209
|
+
# so it is a `dev` requirement and not a build one — a headless runner
|
|
210
|
+
# that builds a release AAB has no reason to carry platform-tools.
|
|
211
|
+
AndroidSdk.adb! if dev
|
|
183
212
|
# A missing compile SDK surfaces from Gradle as "Failed to find target
|
|
184
213
|
# with hash string android-35", minutes in and after the AGP download.
|
|
185
214
|
AndroidSdk.platform!
|
|
@@ -187,6 +216,56 @@ module Everywhere
|
|
|
187
216
|
resolve_icon_font!(template)
|
|
188
217
|
end
|
|
189
218
|
|
|
219
|
+
def check_format!(dev:, format:)
|
|
220
|
+
unless FORMATS.key?(format)
|
|
221
|
+
UI.die!("#{format.inspect} isn't an Android package format — :apk (installable) or :aab " \
|
|
222
|
+
"(what Google Play accepts)")
|
|
223
|
+
end
|
|
224
|
+
return unless format == :aab && dev
|
|
225
|
+
|
|
226
|
+
UI.die!("an .aab can't be installed on a device or an emulator — it's an upload format Play " \
|
|
227
|
+
"re-signs into per-device APKs. `every dev` builds an APK; ask for the bundle when " \
|
|
228
|
+
"you're publishing")
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# --- signing ---------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
# The signing environment Gradle is handed, or nil for the unsigned
|
|
234
|
+
# release the default build still produces. Resolved here rather than left
|
|
235
|
+
# to the build script so a half-configured keystore fails in seconds with
|
|
236
|
+
# the missing variable named, instead of as an AGP error two minutes in —
|
|
237
|
+
# or, worse, as an unsigned APK nobody notices until Play rejects it.
|
|
238
|
+
def signing_env!
|
|
239
|
+
keystore = env_value("EVERY_ANDROID_KEYSTORE") or return nil
|
|
240
|
+
|
|
241
|
+
missing = REQUIRED_SIGNING_ENV.reject { |name| env_value(name) }
|
|
242
|
+
unless missing.empty?
|
|
243
|
+
UI.die!("EVERY_ANDROID_KEYSTORE is set but #{missing.join(" and ")} " \
|
|
244
|
+
"#{missing.one? ? "is" : "are"} not — signing needs the keystore, its password " \
|
|
245
|
+
"and the alias of the key inside it (EVERY_ANDROID_KEY_PASSWORD falls back to " \
|
|
246
|
+
"the keystore password)")
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
path = File.expand_path(keystore)
|
|
250
|
+
UI.die!("EVERY_ANDROID_KEYSTORE points at #{path}, which isn't a file") unless File.file?(path)
|
|
251
|
+
|
|
252
|
+
UI.step("signing the release #{UI.dim("(#{env_value("EVERY_ANDROID_KEY_ALIAS")} in " \
|
|
253
|
+
"#{UI.short_path(path)})")}")
|
|
254
|
+
{ "EVERY_ANDROID_KEYSTORE" => path,
|
|
255
|
+
"EVERY_ANDROID_KEYSTORE_PASSWORD" => env_value("EVERY_ANDROID_KEYSTORE_PASSWORD"),
|
|
256
|
+
"EVERY_ANDROID_KEY_ALIAS" => env_value("EVERY_ANDROID_KEY_ALIAS"),
|
|
257
|
+
"EVERY_ANDROID_KEY_PASSWORD" => env_value("EVERY_ANDROID_KEY_PASSWORD") ||
|
|
258
|
+
env_value("EVERY_ANDROID_KEYSTORE_PASSWORD") }
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# An env var that was set to nothing is the same as unset here: CI writes
|
|
262
|
+
# `EVERY_ANDROID_KEYSTORE: ${{ secrets.KEYSTORE }}` whether or not the
|
|
263
|
+
# secret exists, and an empty keystore path must not look configured.
|
|
264
|
+
def env_value(name)
|
|
265
|
+
value = ENV[name].to_s.strip
|
|
266
|
+
value.empty? ? nil : value
|
|
267
|
+
end
|
|
268
|
+
|
|
190
269
|
# Copy the template into the app's persistent work dir. Merged over
|
|
191
270
|
# whatever is already there, NOT wiped like the iOS stage: Gradle's
|
|
192
271
|
# incremental state (app/build/, .gradle/) lives *inside* this directory,
|
|
@@ -248,7 +327,7 @@ module Everywhere
|
|
|
248
327
|
# belong in config/everywhere.yml.
|
|
249
328
|
everywhere.applicationId=#{properties_value(application_id)}
|
|
250
329
|
everywhere.versionName=#{properties_value(@config.version(target: @target))}
|
|
251
|
-
everywhere.versionCode=#{version_code}
|
|
330
|
+
everywhere.versionCode=#{@version_code}
|
|
252
331
|
everywhere.label=#{properties_value(name)}
|
|
253
332
|
PROPERTIES
|
|
254
333
|
end
|
|
@@ -257,7 +336,20 @@ module Everywhere
|
|
|
257
336
|
# deriving it from the version the app already maintains beats asking for
|
|
258
337
|
# a second number nobody remembers to bump. 1.2.3 → 10203, which stays
|
|
259
338
|
# monotonic as long as minor/patch stay under 100.
|
|
339
|
+
#
|
|
340
|
+
# EVERY_ANDROID_VERSION_CODE overrides it outright, for the pipeline that
|
|
341
|
+
# numbers builds rather than versions them (a run number, a commit count).
|
|
260
342
|
def version_code
|
|
343
|
+
override = env_value(VERSION_CODE_ENV) or return derived_version_code
|
|
344
|
+
|
|
345
|
+
unless override.match?(/\A[1-9]\d*\z/)
|
|
346
|
+
UI.die!("#{VERSION_CODE_ENV}=#{override.inspect} — a versionCode is a positive integer, " \
|
|
347
|
+
"and Play only accepts an upload whose code is higher than the last one's")
|
|
348
|
+
end
|
|
349
|
+
override.to_i
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def derived_version_code
|
|
261
353
|
major, minor, patch = @config.version(target: @target).to_s.split(".").map(&:to_i)
|
|
262
354
|
code = ((major || 0) * 10_000) + ((minor || 0) * 100) + (patch || 0)
|
|
263
355
|
[code, 1].max
|
|
@@ -796,45 +888,67 @@ module Everywhere
|
|
|
796
888
|
|
|
797
889
|
# --- gradle ---------------------------------------------------------------
|
|
798
890
|
|
|
799
|
-
def gradle!(work, variant, dist_dir)
|
|
800
|
-
UI.step("building Android shell #{UI.dim("(Gradle, #{variant}, first
|
|
801
|
-
"Gradle and the Android Gradle Plugin)")}")
|
|
891
|
+
def gradle!(work, variant, format, dist_dir)
|
|
892
|
+
UI.step("building Android shell #{UI.dim("(Gradle, #{variant} #{format.to_s.upcase}, first " \
|
|
893
|
+
"run downloads Gradle and the Android Gradle Plugin)")}")
|
|
802
894
|
FileUtils.mkdir_p(dist_dir)
|
|
803
895
|
log = File.join(dist_dir, "android-build.log")
|
|
804
|
-
task = "#{GRADLE_MODULE}:
|
|
896
|
+
task = "#{GRADLE_MODULE}:#{FORMATS.fetch(format)[:task]}#{variant.capitalize}"
|
|
805
897
|
# -p instead of a chdir: the wrapper finds its own jar from $0, so an
|
|
806
898
|
# absolute gradlew with an explicit project dir works from anywhere
|
|
807
899
|
# (Shellout.run_logged! has no chdir). --console=plain keeps the stream
|
|
808
900
|
# line-oriented for LogFilter whether or not a TTY is attached.
|
|
809
901
|
Shellout.run_logged!(gradle_env,
|
|
810
|
-
[
|
|
902
|
+
[gradle_wrapper(work), "-p", work, "--console=plain", task],
|
|
811
903
|
log: log, filter: LogFilter.for(:gradle))
|
|
812
904
|
end
|
|
813
905
|
|
|
906
|
+
# The wrapper Windows can actually execute — the POSIX gradlew is a shell
|
|
907
|
+
# script there, and cmd.exe won't run it.
|
|
908
|
+
def gradle_wrapper(work)
|
|
909
|
+
File.join(work, AndroidSdk.windows? ? "gradlew.bat" : "gradlew")
|
|
910
|
+
end
|
|
911
|
+
|
|
814
912
|
# The whole toolchain is handed to Gradle explicitly. ANDROID_HOME is what
|
|
815
913
|
# AGP reads when there's no local.properties — and we never write one, so
|
|
816
914
|
# the stamped project stays clean and Android Studio can resolve its own
|
|
817
915
|
# SDK. GRADLE_USER_HOME redirects the 1.4 GB of Gradle distribution, AGP
|
|
818
916
|
# and Hotwire artifacts out of the gem and into a cache shared by every app.
|
|
917
|
+
#
|
|
918
|
+
# The signing variables travel the same way rather than through a file the
|
|
919
|
+
# build script reads: env is the one channel that reaches the Gradle child
|
|
920
|
+
# without leaving a password on disk. The resolved values (absolute
|
|
921
|
+
# keystore path, key password defaulted to the store's) are what's passed,
|
|
922
|
+
# so the build script never has to repeat the fallback.
|
|
819
923
|
def gradle_env
|
|
820
924
|
{ "JAVA_HOME" => @java_home,
|
|
821
925
|
"ANDROID_HOME" => @sdk_root,
|
|
822
926
|
"ANDROID_SDK_ROOT" => @sdk_root,
|
|
823
|
-
"GRADLE_USER_HOME" => Paths.android_gradle_home
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
#
|
|
828
|
-
#
|
|
829
|
-
#
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
927
|
+
"GRADLE_USER_HOME" => Paths.android_gradle_home,
|
|
928
|
+
VERSION_CODE_ENV => @version_code.to_s }.merge(@signing || {})
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
# Copy the built artifact out of the module's build dir. An unsigned
|
|
932
|
+
# release APK is spelled app-release-unsigned.apk by AGP, so both names
|
|
933
|
+
# are accepted — but only when no keystore was configured: with one set,
|
|
934
|
+
# the unsigned name means the signingConfig didn't take, and shipping that
|
|
935
|
+
# silently is how an unsignable APK reaches Play. Bundles have no such
|
|
936
|
+
# spelling; bundleRelease writes app-release.aab either way.
|
|
937
|
+
def collect(work, variant, format, dist_dir)
|
|
938
|
+
spec = FORMATS.fetch(format)
|
|
939
|
+
kind = spec[:extension].upcase
|
|
940
|
+
dir = File.join(work, "app", "build", "outputs", spec[:dir], variant)
|
|
941
|
+
signed = File.join(dir, "app-#{variant}.#{spec[:extension]}")
|
|
942
|
+
unsigned = File.join(dir, "app-#{variant}-unsigned.#{spec[:extension]}")
|
|
943
|
+
built = [signed, unsigned].find { |path| File.file?(path) }
|
|
944
|
+
UI.die!("Gradle reported success but no #{kind} in #{UI.short_path(dir)}") unless built
|
|
945
|
+
|
|
946
|
+
if @signing && built == unsigned
|
|
947
|
+
UI.die!("Gradle wrote #{File.basename(built)} — EVERY_ANDROID_KEYSTORE is set, so the " \
|
|
948
|
+
"#{kind} should have been signed. Check the keystore's alias and passwords")
|
|
949
|
+
end
|
|
836
950
|
|
|
837
|
-
dest = File.join(dist_dir, "#{name}
|
|
951
|
+
dest = File.join(dist_dir, "#{name}.#{spec[:extension]}")
|
|
838
952
|
FileUtils.rm_f(dest)
|
|
839
953
|
FileUtils.cp(built, dest)
|
|
840
954
|
dest
|
|
@@ -10,19 +10,24 @@ module Everywhere
|
|
|
10
10
|
# Resolves where cargo should run for the desktop shell, and stamps a
|
|
11
11
|
# per-app copy of it when the app declares `native.desktop`.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
13
|
+
# EVERY app gets its own staged copy of the shell under
|
|
14
|
+
# ~/.rubyeverywhere/desktop/<id>; cargo never runs in the template. Cargo
|
|
15
|
+
# writes Cargo.lock into the package dir and `every build` swaps the app's
|
|
16
|
+
# icon into icons/icon.png, so compiling the template in place means writing
|
|
17
|
+
# into the installed gem — Errno::EACCES on a system-wide install, and one
|
|
18
|
+
# app's branding in the next app's build when two builds share it.
|
|
14
19
|
#
|
|
15
|
-
#
|
|
16
|
-
# against the SHARED ~/.rubyeverywhere/shell-target.
|
|
17
|
-
# Identical source for every app, so one warm target
|
|
18
|
-
# dir serves all of them. This is what every app did
|
|
19
|
-
# before extensions existed, and it stays free.
|
|
20
|
+
# What the two paths differ in is the stamp and the target dir:
|
|
20
21
|
#
|
|
21
|
-
# native.desktop
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
22
|
+
# no native.desktop the template verbatim, compiled against the SHARED
|
|
23
|
+
# ~/.rubyeverywhere/shell-target. The source really is
|
|
24
|
+
# identical for every such app, so one warm target dir
|
|
25
|
+
# serves all of them.
|
|
26
|
+
#
|
|
27
|
+
# native.desktop the app's Rust stamped in, and its own target dir,
|
|
28
|
+
# exactly like the iOS and Android work dirs. Costs one
|
|
29
|
+
# cold Tauri compile (minutes) and a couple of GB,
|
|
30
|
+
# which is why it is not the default.
|
|
26
31
|
#
|
|
27
32
|
# The stamped list, and nothing else:
|
|
28
33
|
#
|
|
@@ -59,14 +64,14 @@ module Everywhere
|
|
|
59
64
|
# (`every dev`, `every build`) don't need to care which path they got.
|
|
60
65
|
def prepare!
|
|
61
66
|
template = @template_dir || Paths.shell_dir!
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
end
|
|
67
|
+
stamped = @config.native_desktop?
|
|
68
|
+
preflight! if stamped
|
|
65
69
|
|
|
66
|
-
preflight!
|
|
67
70
|
src_tauri = stage(template)
|
|
68
|
-
stamp!(src_tauri)
|
|
69
|
-
Prepared.new(dir: src_tauri,
|
|
71
|
+
stamp!(src_tauri) if stamped
|
|
72
|
+
Prepared.new(dir: src_tauri,
|
|
73
|
+
target_dir: stamped ? Paths.desktop_target_dir(bundle_id) : Paths.cargo_target_dir,
|
|
74
|
+
stamped: stamped)
|
|
70
75
|
end
|
|
71
76
|
|
|
72
77
|
private
|
|
@@ -91,6 +96,9 @@ module Everywhere
|
|
|
91
96
|
#
|
|
92
97
|
# A gem upgrade wipes it: the template's own sources changed, and merging
|
|
93
98
|
# new files over old ones would leave whatever the new template deleted.
|
|
99
|
+
# Re-copying the template every time is also what un-brands the shell: the
|
|
100
|
+
# build swaps the app's icon into the staged icons/icon.png, and the
|
|
101
|
+
# placeholder has to come back on its own when app.icon goes away.
|
|
94
102
|
# `template` points at src-tauri (that's what --shell-dir means), but the
|
|
95
103
|
# whole shell root has to come along: tauri.conf.json's frontendDist is
|
|
96
104
|
# "../splash", a sibling of src-tauri, and generate_context! panics at
|