rubygems-requirements-system 0.0.1 → 0.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b4f3b0dc17cf9b76958081357cc6f9cf1ab338e86a88aad5ed4643d9bca8766
4
- data.tar.gz: 4ebf3a4f17907e0c5ac6ce55a857eeddc5265cb85c6cd229a19f89ca5b738eb2
3
+ metadata.gz: 82e9618b8faccc8a97c5bed8acff02ae39a405e94bb02d9353ad5dc264dda420
4
+ data.tar.gz: f20b793d2dc732c13d2f6c581f103697de2f489afca060eef6e386576f814e3c
5
5
  SHA512:
6
- metadata.gz: dabaddfe7338b0918888b16933b8c110a2bd6a7de9b626e425fab0736b6314d70f3daf48814f4c69d778f6d178f3da2b5e05a626ceec3c37090125f0fc35586e
7
- data.tar.gz: 6f6caa567e5e2d34c680e86e8ba08ca60b1d8f0d6cdfcde0314c90b5db86ae39314796fefe938b009d919a97d9f2c25be1b704144660b034a08222be529d1e6b
6
+ metadata.gz: a9d9abee1cf5832aa8b0d883218addaa3c59cfed9128f3abd836787a2b8e018eb68b04794ded74231691effd09d06dce8809eb01eceae33e8158bce1f7c84558
7
+ data.tar.gz: e420c6d08d4f28c7407622f1417dfbd5b90bf7444146812a0ed6795fd6c829bcd1945a8a24ce22d0660a242cf247861328bda7a983442346d1608c76fbc22cea
data/README.md CHANGED
@@ -52,6 +52,8 @@ Gem::Specification.new do |spec|
52
52
  end
53
53
  ```
54
54
 
55
+ ### Basic usage
56
+
55
57
  Add dependency information to `Gem::Specification#requirements`:
56
58
 
57
59
  ```ruby
@@ -77,6 +79,8 @@ Gem::Specification.new do |spec|
77
79
  end
78
80
  ```
79
81
 
82
+ ### OR dependency
83
+
80
84
  You can require dependency A or B. For example, you can require
81
85
  `mysqlclient` or `libmariadb`.
82
86
 
@@ -85,14 +89,23 @@ Gem::Specification.new do |spec|
85
89
  # ...
86
90
 
87
91
  # We need mysqliclient or libmariadb for this gem.
88
- spec.requirements << "system: mysqlclient|libmariadb: arch_linux: mariadb-libs"
89
- spec.requirements << "system: mysqlclient|libmariadb: debian: libmysqlclient-dev"
92
+
93
+ # Try libmysqlclient-dev and then libmariadb-dev on Ubuntu. Because
94
+ # "debian: libmariadb-dev" is also used on Ubuntu.
95
+ #
96
+ # mysqlclient or libmariadb will be satsfied by a system package.
97
+ spec.requirements << "system: mysqlclient|libmariadb: ubuntu: libmysqlclient-dev"
98
+ # Try only libmariadb-dev on Debian.
99
+ #
100
+ # libmariadb will be satsfied by a system package.
90
101
  spec.requirements << "system: mysqlclient|libmariadb: debian: libmariadb-dev"
91
102
 
92
103
  # ...
93
104
  end
94
105
  ```
95
106
 
107
+ ### Multiple packages per dependency
108
+
96
109
  You can install multiple packages for a dependency.
97
110
 
98
111
  ```ruby
@@ -115,10 +128,224 @@ Gem::Specification.new do |spec|
115
128
  end
116
129
  ```
117
130
 
131
+ ### Install packages via HTTPS
132
+
133
+ You can install `.deb`/`.rpm` via HTTPS. If a product provides its
134
+ APT/Yum repository configurations by `.deb`/`.rpm`, you can use this
135
+ feature to register additional APT/Yum repositories.
136
+
137
+ You can use placeholder for URL with `%{KEY}` format.
138
+
139
+ Here are available placeholders:
140
+
141
+ `debian` family platforms:
142
+
143
+ * `distribution`: The `ID` value in `/etc/os-release`. It's `debian`,
144
+ `ubuntu` and so on.
145
+ * `code_name`: The `VERSION_CODENAME` value in `/etc/os-release`. It's
146
+ `bookworm`, `noble` and so on.
147
+ * `version`: The `VERSION_ID` value in `/etc/os-release`. It's `12`,
148
+ `24.04` and so on.
149
+
150
+ `fedora` family platforms:
151
+
152
+ * `distribution`: The `ID` value in `/etc/os-release`. It's `fedora`,
153
+ `rhel`, `almalinux` and so on.
154
+ * `major_version`: The major part of `VERSION_ID` value in
155
+ `/etc/os-release`. It's `41`, `9` and so on.
156
+ * `version`: The `VERSION_ID` value in `/etc/os-release`. It's `41`,
157
+ `9.5` and so on.
158
+
159
+ Here is an example that uses this feature for adding new repositories:
160
+
161
+ ```ruby
162
+ Gem::Specification.new do |spec|
163
+ # ...
164
+
165
+ # Install Groonga's APT repository for libgroonga-dev on Debian
166
+ # family platforms.
167
+ #
168
+ # %{distribution} and %{code_name} are placeholders.
169
+ #
170
+ # On Debian GNU/Linux bookworm:
171
+ # https://packages.groonga.org/%{distribution}/groonga-apt-source-latest-%{code_name}.deb ->
172
+ # https://packages.groonga.org/debian/groonga-apt-source-latest-bookworm.deb
173
+ #
174
+ # On Ubuntu 24.04:
175
+ # https://packages.groonga.org/%{distribution}/groonga-apt-source-latest-%{code_name}.deb ->
176
+ # https://packages.groonga.org/ubuntu/groonga-apt-source-latest-noble.deb
177
+ spec.requirements << "system: groonga: debian: https://packages.groonga.org/%{distribution}/groonga-apt-source-latest-%{code_name}.deb"
178
+ # Install libgroonga-dev from the registered repository.
179
+ spec.requirements << "system: groonga: debian: libgroonga-dev"
180
+
181
+ # Install 2 repositories for pkgconfig(groonga) package on RHEL
182
+ # family plaforms:
183
+ # 1. Apache Arrow: https://apache.jfrog.io/artifactory/arrow/almalinux/%{major_version}/apache-arrow-release-latest.rpm
184
+ # 2. Groonga: https://packages.groonga.org/almalinux/%{major_version}/groonga-release-latest.noarch.rpm
185
+ #
186
+ # %{major_version} is placeholder.
187
+ #
188
+ # On AlmaLinux 8:
189
+ # https://apache.jfrog.io/artifactory/arrow/almalinux/%{major_version}/apache-arrow-release-latest.rpm ->
190
+ # https://apache.jfrog.io/artifactory/arrow/almalinux/8/apache-arrow-release-latest.rpm
191
+ #
192
+ # https://packages.groonga.org/almalinux/%{major_version}/groonga-release-latest.noarch.rpm ->
193
+ # https://packages.groonga.org/almalinux/8/groonga-release-latest.noarch.rpm
194
+ #
195
+ # On AlmaLinux 9:
196
+ # https://apache.jfrog.io/artifactory/arrow/almalinux/%{major_version}/apache-arrow-release-latest.rpm ->
197
+ # https://apache.jfrog.io/artifactory/arrow/almalinux/9/apache-arrow-release-latest.rpm
198
+ #
199
+ # https://packages.groonga.org/almalinux/%{major_version}/groonga-release-latest.noarch.rpm ->
200
+ # https://packages.groonga.org/almalinux/9/groonga-release-latest.noarch.rpm
201
+ spec.requirements << "system: groonga: rhel: https://apache.jfrog.io/artifactory/arrow/almalinux/%{major_version}/apache-arrow-release-latest.rpm"
202
+ spec.requirements << "system: groonga: rhel: https://packages.groonga.org/almalinux/%{major_version}/groonga-release-latest.noarch.rpm"
203
+ # Install pkgconfig(groonga) from the registered repositories.
204
+ spec.requirements << "system: groonga: rhel: pkgconfig(groonga)"
205
+
206
+ # ...
207
+ end
208
+ ```
209
+
210
+ ### Install repositories
211
+
212
+ You can install APT/Yum repositories by specifying metadata.
213
+
214
+ You need to specify multiple metadata for one repository. So you need
215
+ to use multiple `spec.requirements` for one repository. Here is the
216
+ syntax for one repository:
217
+
218
+ ```ruby
219
+ spec.requirements << "system: #{package}: #{platform}: repository: #{key1}: #{value1}"
220
+ spec.requirements << "system: #{package}: #{platform}: repository: #{key2}: #{value2}"
221
+ # ...
222
+ ```
223
+
224
+ You must specify at least `id` as `key`. For example:
225
+
226
+ ```
227
+ spec.requirements << "system: libpq: debian: repository: id: pgdg"
228
+ ```
229
+
230
+ You can start another repository metadata by starting `id` metadata
231
+ for another repository:
232
+
233
+ ```ruby
234
+ spec.requirements << "system: #{package}: #{platform}: repository: id: repository1
235
+ spec.requirements << "system: #{package}: #{platform}: repository: #{key1_1}: #{value1_1}"
236
+ spec.requirements << "system: #{package}: #{platform}: repository: #{key1_2}: #{value1_2}"
237
+ # ...
238
+ spec.requirements << "system: #{package}: #{platform}: repository: id: repository2
239
+ spec.requirements << "system: #{package}: #{platform}: repository: #{key2_1}: #{value2_1}"
240
+ spec.requirements << "system: #{package}: #{platform}: repository: #{key2_2}: #{value2_2}"
241
+ # ...
242
+ ```
243
+
244
+ Here are metadata for a APT repository:
245
+
246
+ * `compoennts`: Optional. The default is `main`.
247
+ * `signed-by`: Optional. The URL of armored keyring that is used for
248
+ signing this repository.
249
+ * `suites`: Optional. The default is `%{code_name}`.
250
+ * `types`: Optional. The default is `deb`.
251
+ * `uris`: Required. The URLs that provide this repository.
252
+
253
+ See also: https://wiki.debian.org/SourcesList
254
+
255
+ Here are metadata for a Yum repository:
256
+
257
+ * `baseurl`: Required. The base URL that provides this repository.
258
+ * `gpgkey`: Optional. The URL of GPG key that is used for signing this
259
+ repository.
260
+ * `name`: Optional. The name of this repository.
261
+
262
+ See also: TODO: Is there any URL that describes the specification of
263
+ `.repo` file?
264
+
265
+ You can use placeholder for metadata values with `%{KEY}` format.
266
+
267
+ Here are available placeholders:
268
+
269
+ `debian` family platforms:
270
+
271
+ * `distribution`: The `ID` value in `/etc/os-release`. It's `debian`,
272
+ `ubuntu` and so on.
273
+ * `code_name`: The `VERSION_CODENAME` value in `/etc/os-release`. It's
274
+ `bookworm`, `noble` and so on.
275
+ * `version`: The `VERSION_ID` value in `/etc/os-release`. It's `12`,
276
+ `24.04` and so on.
277
+
278
+ `fedora` family platforms:
279
+
280
+ * `distribution`: The `ID` value in `/etc/os-release`. It's `fedora`,
281
+ `rhel`, `almalinux` and so on.
282
+ * `major_version`: The major part of `VERSION_ID` value in
283
+ `/etc/os-release`. It's `41`, `9` and so on.
284
+ * `version`: The `VERSION_ID` value in `/etc/os-release`. It's `41`,
285
+ `9.5` and so on.
286
+
287
+ Here is an example that uses this feature for adding a new repository:
288
+
289
+ ```ruby
290
+ Gem::Specification.new do |spec|
291
+ # ...
292
+
293
+ # Install PostgreSQL's APT repository on Debian family platforms.
294
+ #
295
+ # %{code_name} is placeholders.
296
+ #
297
+ # On Debian GNU/Linux bookworm:
298
+ # %{code_name}-pgdg ->
299
+ # bookworm-pgdg
300
+ #
301
+ # On Ubuntu 24.04:
302
+ # %{code_name}-pgdg ->
303
+ # noble-pgdg
304
+ spec.requirements << "system: libpq: debian: repository: id: pgdg"
305
+ spec.requirements << "system: libpq: debian: repository: uris: https://apt.postgresql.org/pub/repos/apt"
306
+ spec.requirements << "system: libpq: debian: repository: signed-by: https://www.postgresql.org/media/keys/ACCC4CF8.asc"
307
+ spec.requirements << "system: libpq: debian: repository: suites: %{code_name}-pgdg"
308
+ spec.requirements << "system: libpq: debian: repository: components: main"
309
+ # Install libpq-dev from the registered repository.
310
+ spec.requirements << "system: libpq: debian: libpq-dev"
311
+
312
+ # Install PostgreSQL's Yum repository on RHEL family platforms:
313
+ spec.requirements << "system: libpq: rhel: repository: id: pgdg17"
314
+ spec.requirements << "system: libpq: rhel: repository: name: PostgreSQL 17 $releasever - $basearch"
315
+ spec.requirements << "system: libpq: rhel: repository: baseurl: https://download.postgresql.org/pub/repos/yum/17/redhat/rhel-$releasever-$basearch"
316
+ spec.requirements << "system: libpq: rhel: repository: gpgkey: https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-RHEL"
317
+ # You can disable built-in "postgresql" module by "module: disable:
318
+ # postgresql".
319
+ spec.requirements << "system: libpq: rhel: module: disable: postgresql"
320
+ # Install postgresql17-devel from the registered repository. But
321
+ # users can't find "libpq.pc" provided by postgresql17-devel without
322
+ # PKG_CONFIG_PATH=/usr/pgsql-17/lib/pkgconfig ...
323
+ spec.requirements << "system: libpq: rhel: postgresql17-devel"
324
+
325
+ # ...
326
+ end
327
+ ```
328
+
329
+ ## Configurations
330
+
331
+ ### Opt-out
332
+
333
+ If you don't like that gems may install system packages automatically,
334
+ you can disable this feature by the followings:
335
+
336
+ 1. Set `RUBYGEMS_REQUIREMENTS_SYSTEM=false`
337
+ 2. Add the following configuration to `~/.gemrc`:
338
+
339
+ ```yaml
340
+ requirements_system:
341
+ enabled: true
342
+ ```
343
+
118
344
  ## Requirements
119
345
 
120
- RubyGems 3.5.0 or later is required. RubyGems can load installed
121
- plugin immediately since 3.5.0.
346
+ RubyGems 3.4.14 or later is required. RubyGems can load installed
347
+ plugin immediately since 3.4.14. Ruby 3.2.3 or later ships RubyGems
348
+ 3.4.14 or later.
122
349
 
123
350
  If `gem install glib2` installs rubygems-requirements-system gem as a
124
351
  dependency, old RubyGems doesn't use a RubyGems plugin in
@@ -136,6 +363,6 @@ other word than "native". So we create a new gem.
136
363
 
137
364
  ## License
138
365
 
139
- Copyright (C) 2013-2025 Ruby-GNOME Project Team
366
+ Copyright (C) 2025 Ruby-GNOME Project Team
140
367
 
141
368
  LGPL-3 or later. See doc/text/lgpl-3.txt for details.
data/doc/text/news.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # News
2
2
 
3
+ ## 0.0.3 - 2025-01-13
4
+
5
+ ### Improvements
6
+
7
+ * Added support for opt-out.
8
+
9
+ * debian: Added support for adding APT repository by raw metadata.
10
+
11
+ * fedora: Added support for adding Yum repository by raw metadata.
12
+
13
+ * fedora: Added support for enabling/disabling module.
14
+
15
+ ## 0.0.2 - 2025-01-08
16
+
17
+ ### Improvements
18
+
19
+ * debian: Added support for installing `.deb` via HTTPS.
20
+
21
+ * ubuntu: Added support for PPA repository.
22
+
23
+ * fedora: Added support for installing `.rpm` via HTTPS.
24
+
3
25
  ## 0.0.1 - 2025-01-08
4
26
 
5
27
  Initial release!!!
@@ -20,40 +20,9 @@ require "pkg-config"
20
20
  require_relative "version"
21
21
 
22
22
  require_relative "platform"
23
+ require_relative "requirements-parser"
23
24
 
24
25
  module RubyGemsRequirementsSystem
25
- Package = Struct.new(:id, :operator, :version) do
26
- def valid?
27
- return false if id.empty?
28
- return false if operator and version.nil?
29
- true
30
- end
31
- end
32
-
33
- Requirement = Struct.new(:packages, :system_packages) do
34
- def satisfied?
35
- packages.any? do |package|
36
- installed?(package)
37
- end
38
- end
39
-
40
- private
41
- def installed?(package)
42
- package_config = PKGConfig.package_config(package.id)
43
- begin
44
- package_config.cflags
45
- rescue PackageConfig::NotFoundError
46
- return false
47
- end
48
-
49
- return true if package.version.nil?
50
-
51
- current_version = Gem::Version.new(package_config.version)
52
- required_version = Gem::Version.new(package.version)
53
- current_version.__send__(package.operator, required_version)
54
- end
55
- end
56
-
57
26
  class Installer
58
27
  include Gem::UserInteraction
59
28
 
@@ -63,7 +32,10 @@ module RubyGemsRequirementsSystem
63
32
  end
64
33
 
65
34
  def install
66
- requirements = parse_requirements(@gemspec.requirements)
35
+ return true unless enabled?
36
+
37
+ parser = RequirementsParser.new(@gemspec.requirements, @platform)
38
+ requirements = parser.parse
67
39
  requirements.all? do |requirement|
68
40
  next true if requirement.satisfied?
69
41
  @platform.install(requirement)
@@ -71,52 +43,21 @@ module RubyGemsRequirementsSystem
71
43
  end
72
44
 
73
45
  private
74
- def parse_requirements(gemspec_requirements)
75
- all_packages_set = {}
76
- requirements = {}
77
- gemspec_requirements.each do |gemspec_requirement|
78
- components = gemspec_requirement.split(/: +/, 4)
79
- next unless components.size == 4
80
-
81
- id, raw_packages, platform, system_package = components
82
- next unless id == "system"
83
-
84
- packages = parse_packages(raw_packages)
85
- next if packages.empty?
86
-
87
- all_packages_set[packages] = true
88
-
89
- next unless @platform.target?(platform)
90
- requirements[packages] ||= []
91
- requirements[packages] << system_package
92
- end
93
- (all_packages_set.keys - requirements.keys).each do |not_used_packages|
94
- system_packages = @platform.default_system_packages(not_used_packages)
95
- next if system_packages.nil?
96
- requirements[not_used_packages] = system_packages
97
- end
98
- requirements.collect do |packages, system_packages|
99
- Requirement.new(packages, system_packages)
46
+ def enabled?
47
+ case ENV["RUBYGEMS_REQUIREMENTS_SYSTEM"]
48
+ when "0", "no", "NO", "false", "FALSE"
49
+ return false
100
50
  end
101
- end
102
51
 
103
- def parse_packages(raw_packages)
104
- packages = raw_packages.split(/\s*\|\s*/).collect do |raw_package|
105
- Package.new(*raw_package.split(/\s*(==|!=|>=|>|<=|<)\s*/, 3))
106
- end
107
- # Ignore this requirement if any invalid package is included.
108
- # We must not report an error for this because
109
- # Gem::Specification#requirements is a free form
110
- # configuration. So there are configuration values that use
111
- # "system: ..." but not for this plugin. We can report a
112
- # warning instead.
113
- packages.each do |package|
114
- unless package.valid?
115
- # TODO: Report a warning
116
- return []
117
- end
52
+ requirements_system = Gem.configuration["requirements_system"] || {}
53
+ case requirements_system["enabled"]
54
+ when false
55
+ return false
56
+ when "false" # "true"/"false" isn't converted to boolean with old RubyGems
57
+ return false
118
58
  end
119
- packages
59
+
60
+ true
120
61
  end
121
62
  end
122
63
  end
@@ -34,13 +34,21 @@ module RubyGemsRequirementsSystem
34
34
  end
35
35
 
36
36
  def id_like
37
- (self["ID_LIKE"] || "").split(/ /)
37
+ (self["ID_LIKE"] || "").split(/\s+/)
38
38
  end
39
39
 
40
40
  def version
41
41
  self["VERSION"]
42
42
  end
43
43
 
44
+ def version_codename
45
+ self["VERSION_CODENAME"]
46
+ end
47
+
48
+ def version_id
49
+ self["VERSION_ID"]
50
+ end
51
+
44
52
  private
45
53
  def parse
46
54
  @variables = {}
@@ -14,4 +14,25 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
16
  module RubyGemsRequirementsSystem
17
+ Package = Struct.new(:id, :operator, :required_version) do
18
+ class << self
19
+ def parse(input)
20
+ new(*input.split(/\s*(==|>=|>|<=|<)\s*/, 3))
21
+ end
22
+ end
23
+
24
+ def valid?
25
+ return false if id.empty?
26
+ return false if operator and required_version.nil?
27
+ true
28
+ end
29
+
30
+ def satisfied?(target_version)
31
+ return true if required_version.nil?
32
+
33
+ target = Gem::Version.new(target_version)
34
+ required = Gem::Version.new(required_version)
35
+ target.__send__(operator, required)
36
+ end
37
+ end
17
38
  end
@@ -14,6 +14,9 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
16
  require "fileutils"
17
+ require "open-uri"
18
+ require "rubygems/user_interaction"
19
+ require "tempfile"
17
20
 
18
21
  require_relative "../executable-finder"
19
22
  require_relative "../os-release"
@@ -31,6 +34,14 @@ module RubyGemsRequirementsSystem
31
34
  nil
32
35
  end
33
36
 
37
+ def valid_system_package?(package)
38
+ true
39
+ end
40
+
41
+ def valid_system_repository?(repository)
42
+ false
43
+ end
44
+
34
45
  def install(requirement)
35
46
  synchronize do
36
47
  requirement.system_packages.any? do |package|
@@ -79,24 +90,46 @@ module RubyGemsRequirementsSystem
79
90
  super_user?
80
91
  end
81
92
 
93
+ def temporary_file_scope
94
+ @temporary_files = []
95
+ begin
96
+ yield
97
+ ensure
98
+ @temporary_files.each(&:close!)
99
+ end
100
+ end
101
+
102
+ def create_temporary_file(basename)
103
+ file = Tempfile.new(basename)
104
+ @temporary_files << file
105
+ file
106
+ end
107
+
82
108
  def install_package(package)
83
- command_line = prepare_command_line(package)
84
- if command_line
85
- unless run_command_line(package, "prepare", command_line)
86
- return false
109
+ temporary_file_scope do
110
+ prepare_command_lines(package).each do |command_line|
111
+ unless run_command_line(package, "prepare", command_line)
112
+ return false
113
+ end
87
114
  end
115
+ run_command_line(package, "install", install_command_line(package))
88
116
  end
89
- run_command_line(package, "install", install_command_line(package))
90
117
  end
91
118
 
92
119
  def run_command_line(package, action, command_line)
120
+ if package.is_a?(SystemRepository)
121
+ package_label = "repository(#{package.id})"
122
+ else
123
+ package_label = package
124
+ end
125
+ prefix = "requirements: system: #{package_label}: #{action}"
126
+ say("#{prefix}: Start")
93
127
  failed_to_get_super_user_priviledge = false
94
- command_line = install_command_line(package)
95
128
  if have_priviledge?
96
129
  succeeded = system(*command_line)
97
130
  else
98
131
  if sudo
99
- prompt = "[sudo] password for %u to #{action} <#{package}>: "
132
+ prompt = "[sudo] password for %u to #{action} <#{package_label}>: "
100
133
  command_line = [sudo, "-p", prompt, *command_line]
101
134
  succeeded = system(*command_line)
102
135
  else
@@ -109,22 +142,22 @@ module RubyGemsRequirementsSystem
109
142
  else
110
143
  result_message = succeeded ? "succeeded" : "failed"
111
144
  end
112
- say("#{action.capitalize} '#{package}' system package: #{result_message}")
145
+ say("#{prefix}: #{result_message}")
113
146
 
114
147
  unless succeeded
115
148
  escaped_command_line = command_line.collect do |part|
116
149
  Shellwords.escape(part)
117
150
  end
118
- alert_warning("'#{package}' system package is required.")
151
+ alert_warning("#{prefix}: Failed.")
119
152
  alert_warning("Run the following command " +
120
153
  "to #{action} required system package: " +
121
- escaped_command.join(" "))
154
+ escaped_command_line.join(" "))
122
155
  end
123
156
  succeeded
124
157
  end
125
158
 
126
- def prepare_command_line(package)
127
- nil
159
+ def prepare_command_lines(package)
160
+ []
128
161
  end
129
162
 
130
163
  def install_command_line(package)
@@ -42,12 +42,90 @@ module RubyGemsRequirementsSystem
42
42
  end
43
43
  end
44
44
 
45
+ def valid_system_repository?(repository)
46
+ uris = repository["uris"]
47
+ return false if uris.nil?
48
+ return false if uris.empty?
49
+
50
+ true
51
+ end
52
+
45
53
  private
46
- def prepare_command_line(package)
47
- ["apt-get", "update"]
54
+ def prepare_command_lines(package)
55
+ [
56
+ ["apt-get", "update"],
57
+ ]
48
58
  end
49
59
 
50
60
  def install_command_line(package)
61
+ if package.is_a?(SystemRepository)
62
+ install_command_line_repository(package)
63
+ else
64
+ install_command_line_package(package)
65
+ end
66
+ end
67
+
68
+ def install_command_line_repository(repository)
69
+ temporary_file_base_name = [
70
+ "rubygems-requirements-system-debian-#{repository.id}",
71
+ ".sources",
72
+ ]
73
+ sources = create_temporary_file(temporary_file_base_name)
74
+ signed_by_url = resolve_value_template(repository["signed-by"])
75
+ if signed_by_url
76
+ signed_by = URI.open(signed_by_url) do |response|
77
+ response.read
78
+ end
79
+ else
80
+ signed_by = nil
81
+ end
82
+ suites = repository["suites"] || "%{code_name}"
83
+ components = repository["components"] || "main"
84
+ {
85
+ "Types" => repository["types"] || "deb",
86
+ "URIs" => resolve_value_template(repository["uris"]),
87
+ "Suites" => resolve_value_template(suites),
88
+ "Components" => resolve_value_template(components),
89
+ "Signed-By" => signed_by,
90
+ }.each do |key, value|
91
+ next if value.nil?
92
+ if value.include?("\n")
93
+ sources.puts("#{key}:")
94
+ value.each_line(chomp: true) do |line|
95
+ if line.empty?
96
+ sources.puts(" .")
97
+ else
98
+ sources.puts(" #{line}")
99
+ end
100
+ end
101
+ else
102
+ sources.puts("#{key}: #{value}")
103
+ end
104
+ end
105
+ sources.close
106
+ FileUtils.chmod("go+r", sources.path)
107
+ [
108
+ "cp",
109
+ sources.path,
110
+ "/etc/apt/sources.list.d/#{repository.id}.sources",
111
+ ]
112
+ end
113
+
114
+ def install_command_line_package(package)
115
+ if package.start_with?("https://")
116
+ package_url = resolve_value_template(package)
117
+ temporary_file_base_name = [
118
+ "rubygems-requirements-system-debian",
119
+ File.extname(package),
120
+ ]
121
+ local_package = create_temporary_file(temporary_file_base_name)
122
+ URI.open(package_url) do |response|
123
+ IO.copy_stream(response, local_package)
124
+ end
125
+ local_package.close
126
+ FileUtils.chmod("go+r", local_package.path)
127
+ package = local_package.path
128
+ end
51
129
  [
52
130
  "env",
53
131
  "DEBIAN_FRONTEND=noninteractive",
@@ -62,6 +140,17 @@ module RubyGemsRequirementsSystem
62
140
  def need_super_user_priviledge?
63
141
  true
64
142
  end
143
+
144
+ def resolve_value_template(template)
145
+ return nil if template.nil?
146
+
147
+ os_release = OSRelease.new
148
+ template % {
149
+ distribution: os_release.id,
150
+ code_name: os_release.version_codename,
151
+ version: os_release.version_id,
152
+ }
153
+ end
65
154
  end
66
155
  end
67
156
  end
@@ -13,11 +13,11 @@
13
13
  # You should have received a copy of the GNU Lesser General Public License
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
- require_relative "red-hat-enterprise-linux"
16
+ require_relative "base"
17
17
 
18
18
  module RubyGemsRequirementsSystem
19
19
  module Platform
20
- class Fedora < RedHatEnterpriseLinux
20
+ class Fedora < Base
21
21
  Platform.register(self)
22
22
 
23
23
  class << self
@@ -28,17 +28,110 @@ module RubyGemsRequirementsSystem
28
28
  end
29
29
 
30
30
  def target?(platform)
31
- platform == "fedora" || super
31
+ platform == "fedora"
32
+ end
33
+
34
+ def valid_system_package?(package)
35
+ return true unless package.start_with?("module:")
36
+
37
+ action, target = parse_module_system_package(package)
38
+ case action
39
+ when "enable", "disable"
40
+ else
41
+ return false
42
+ end
43
+ return false if target.nil?
44
+
45
+ true
46
+ end
47
+
48
+ def valid_system_repository?(repository)
49
+ baseurl = repository["baseurl"]
50
+ return false if baseurl.nil?
51
+ return false if baseurl.empty?
52
+
53
+ true
54
+ end
55
+
56
+ def default_system_packages(packages)
57
+ packages.collect {|package| "pkgconfig(#{package.id})"}
32
58
  end
33
59
 
34
60
  private
61
+ def parse_module_system_package(package)
62
+ # "module: disable: postgresql" ->
63
+ # ["module", "disable", "postgresql"]
64
+ _, action, target = package.split(/:\s*/, 3)
65
+ [action, target]
66
+ end
67
+
35
68
  def install_command_line(package)
36
- ["dnf", "install", "-y", package]
69
+ if package.is_a?(SystemRepository)
70
+ install_command_line_repository(package)
71
+ elsif package.start_with?("module:")
72
+ install_command_line_module(package)
73
+ else
74
+ install_command_line_package(package)
75
+ end
76
+ end
77
+
78
+ def install_command_line_repository(repository)
79
+ temporary_file_base_name = [
80
+ "rubygems-requirements-system-fedora-#{repository.id}",
81
+ ".repo",
82
+ ]
83
+ repo = create_temporary_file(temporary_file_base_name)
84
+ repo.puts("[#{repository.id}]")
85
+ {
86
+ "name" => resolve_value_template(repository["name"]),
87
+ "baseurl" => resolve_value_template(repository["baseurl"]),
88
+ "enabled" => "1",
89
+ "gpgcheck" => repository["gpgkey"] ? "1" : "0",
90
+ "gpgkey" => resolve_value_template(repository["gpgkey"]),
91
+ }.each do |key, value|
92
+ next if value.nil?
93
+ repo.puts("#{key}=#{value}")
94
+ end
95
+ repo.close
96
+ FileUtils.chmod("go+r", repo.path)
97
+ [
98
+ "cp",
99
+ repo.path,
100
+ "/etc/yum.repos.d/#{repository.id}.repo",
101
+ ]
102
+ end
103
+
104
+ def install_command_line_module(package)
105
+ action, target = parse_module_system_package(package)
106
+ ["dnf", "-y", "module", action, target]
107
+ end
108
+
109
+ def install_command_line_package(package)
110
+ if package.start_with?("https://")
111
+ package = resolve_value_template(package)
112
+ end
113
+ ["dnf", "-y", "install", package]
37
114
  end
38
115
 
39
116
  def need_super_user_priviledge?
40
117
  true
41
118
  end
119
+
120
+ def resolve_value_template(template)
121
+ return nil if template.nil?
122
+
123
+ os_release = OSRelease.new
124
+ template % {
125
+ distribution: os_release.id,
126
+ major_version: major_version.to_s,
127
+ version: os_release.version_id,
128
+ }
129
+ end
130
+
131
+ def major_version
132
+ major_version_string = OSRelease.new.version_id.split(".")[0]
133
+ Integer(major_version_string, 10)
134
+ end
42
135
  end
43
136
  end
44
137
  end
@@ -13,11 +13,11 @@
13
13
  # You should have received a copy of the GNU Lesser General Public License
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
- require_relative "base"
16
+ require_relative "fedora"
17
17
 
18
18
  module RubyGemsRequirementsSystem
19
19
  module Platform
20
- class RedHatEnterpriseLinux < Base
20
+ class RedHatEnterpriseLinux < Fedora
21
21
  Platform.register(self)
22
22
 
23
23
  class << self
@@ -34,34 +34,27 @@ module RubyGemsRequirementsSystem
34
34
  when "redhat" # For backward compatibility
35
35
  true
36
36
  else
37
- false
37
+ super
38
38
  end
39
39
  end
40
40
 
41
- def default_system_packages(packages)
42
- packages.collect {|package| "pkgconfig(#{package.id})"}
43
- end
44
-
45
41
  private
46
-
47
- def install_command_line(package)
42
+ def install_command_line_package(package)
43
+ if package.start_with?("https://")
44
+ package = resolve_value_template(package)
45
+ end
48
46
  if major_version >= 9
49
- ["dnf", "install", "--enablerepo=crb", "-y", package]
47
+ ["dnf", "-y", "install", "--enablerepo=crb", package]
50
48
  elsif major_version >= 8
51
- ["dnf", "install", "--enablerepo=powertools", "-y", package]
49
+ ["dnf", "-y", "install", "--enablerepo=powertools", package]
52
50
  else
53
- ["yum", "install", "-y", package]
51
+ ["yum", "-y", "install", package]
54
52
  end
55
53
  end
56
54
 
57
55
  def need_super_user_priviledge?
58
56
  true
59
57
  end
60
-
61
- def major_version
62
- major_version_string = File.read("/etc/redhat-release")[/(\d+)/, 0]
63
- Integer(major_version_string, 10)
64
- end
65
58
  end
66
59
  end
67
60
  end
@@ -36,6 +36,30 @@ module RubyGemsRequirementsSystem
36
36
  def target?(platform)
37
37
  platform == "ubuntu" || super
38
38
  end
39
+
40
+ private
41
+ def prepare_command_lines(package)
42
+ if package.is_a?(String) and package.start_with?("ppa:")
43
+ [
44
+ ["apt-get", "update"],
45
+ install_command_line("software-properties-common"),
46
+ ]
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ def install_command_line_package(package)
53
+ if package.start_with?("ppa:")
54
+ [
55
+ "add-apt-repository",
56
+ "-y",
57
+ package,
58
+ ]
59
+ else
60
+ super
61
+ end
62
+ end
39
63
  end
40
64
  end
41
65
  end
@@ -0,0 +1,36 @@
1
+ # Copyright (C) 2025 Ruby-GNOME Project Team
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ module RubyGemsRequirementsSystem
17
+ Requirement = Struct.new(:packages, :system_packages) do
18
+ def satisfied?
19
+ packages.any? do |package|
20
+ installed?(package)
21
+ end
22
+ end
23
+
24
+ private
25
+ def installed?(package)
26
+ package_config = PKGConfig.package_config(package.id)
27
+ begin
28
+ package_config.cflags
29
+ rescue PackageConfig::NotFoundError
30
+ return false
31
+ end
32
+
33
+ package.satisfied?(package_config.version)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,112 @@
1
+ # Copyright (C) 2025 Ruby-GNOME Project Team
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require_relative "package"
17
+ require_relative "requirement"
18
+ require_relative "system-repository"
19
+
20
+ module RubyGemsRequirementsSystem
21
+ class RequirementsParser
22
+ def initialize(gemspec_requirements, platform)
23
+ @gemspec_requirements = gemspec_requirements
24
+ @platform = platform
25
+ end
26
+
27
+ def parse
28
+ all_packages_set = {}
29
+ requirements = {}
30
+ @gemspec_requirements.each do |gemspec_requirement|
31
+ components = gemspec_requirement.split(/:\s*/, 4)
32
+ next unless components.size == 4
33
+
34
+ id, raw_packages, platform, system_package = components
35
+ next unless id == "system"
36
+
37
+ packages = parse_packages(raw_packages)
38
+ next if packages.empty?
39
+
40
+ all_packages_set[packages] = true
41
+
42
+ next unless @platform.target?(platform)
43
+ requirements[packages] ||= []
44
+ requirements[packages] << system_package
45
+ end
46
+ (all_packages_set.keys - requirements.keys).each do |not_used_packages|
47
+ system_packages = @platform.default_system_packages(not_used_packages)
48
+ next if system_packages.nil?
49
+ requirements[not_used_packages] = system_packages
50
+ end
51
+ requirements.collect do |packages, system_packages|
52
+ system_packages = detect_system_repositories(system_packages)
53
+ Requirement.new(packages, system_packages)
54
+ end
55
+ end
56
+
57
+ private
58
+ def parse_packages(raw_packages)
59
+ packages = raw_packages.split(/\s*\|\s*/).collect do |raw_package|
60
+ Package.parse(raw_package)
61
+ end
62
+ # Ignore this requirement if any invalid package is included.
63
+ # We must not report an error for this because
64
+ # Gem::Specification#requirements is a free form
65
+ # configuration. So there are configuration values that use
66
+ # "system: ..." but not for this plugin. We can report a
67
+ # warning instead.
68
+ packages.each do |package|
69
+ unless package.valid?
70
+ # TODO: Report a warning
71
+ return []
72
+ end
73
+ end
74
+ packages
75
+ end
76
+
77
+ def detect_system_repositories(original_system_packages)
78
+ system_packages = []
79
+ repository_properties = {}
80
+
81
+ flush_repository = lambda do
82
+ return if repository_properties.empty?
83
+ properties = repository_properties
84
+ repository_properties = {}
85
+
86
+ id = properties.delete("id")
87
+ return if id.nil?
88
+ repository = SystemRepository.new(id, properties)
89
+ return unless @platform.valid_system_repository?(repository)
90
+ system_packages << repository
91
+ end
92
+
93
+ original_system_packages.each do |system_package|
94
+ unless system_package.start_with?("repository:")
95
+ flush_repository.call
96
+ next unless @platform.valid_system_package?(system_package)
97
+ system_packages << system_package
98
+ next
99
+ end
100
+ _, key, value = system_package.strip.split(/:\s*/, 3)
101
+ next if value.empty?
102
+ if key == "id" and repository_properties.key?("id")
103
+ flush_repository.call
104
+ end
105
+ repository_properties[key] = value
106
+ end
107
+ flush_repository.call
108
+
109
+ system_packages
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright (C) 2025 Ruby-GNOME Project Team
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ module RubyGemsRequirementsSystem
17
+ class SystemRepository
18
+ attr_reader :id
19
+ attr_reader :properties
20
+ protected :properties
21
+ def initialize(id, properties)
22
+ @id = id
23
+ @properties = properties
24
+ end
25
+
26
+ def ==(other)
27
+ other.is_a?(self.class) and
28
+ @id == other.id and
29
+ @properties == other.properties
30
+ end
31
+
32
+ def eql?(other)
33
+ self == other
34
+ end
35
+
36
+ def hash
37
+ [@id, @properties].hash
38
+ end
39
+
40
+ def [](key)
41
+ @properties[key]
42
+ end
43
+
44
+ def each(&block)
45
+ @properties.each(&block)
46
+ end
47
+ end
48
+ end
@@ -14,5 +14,5 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
16
  module RubyGemsRequirementsSystem
17
- VERSION = "0.0.1"
17
+ VERSION = "0.0.3"
18
18
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubygems-requirements-system
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sutou Kouhei
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-01-08 00:00:00.000000000 Z
10
+ date: 2025-01-13 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: pkg-config
@@ -66,13 +65,15 @@ files:
66
65
  - lib/rubygems-requirements-system/platform/suse.rb
67
66
  - lib/rubygems-requirements-system/platform/ubuntu.rb
68
67
  - lib/rubygems-requirements-system/platform/unknown.rb
68
+ - lib/rubygems-requirements-system/requirement.rb
69
+ - lib/rubygems-requirements-system/requirements-parser.rb
70
+ - lib/rubygems-requirements-system/system-repository.rb
69
71
  - lib/rubygems-requirements-system/version.rb
70
72
  - lib/rubygems_plugin.rb
71
73
  homepage: https://github.com/ruby-gnome/rubygems-requirements-system
72
74
  licenses:
73
75
  - LGPL-3.0-or-later
74
76
  metadata: {}
75
- post_install_message:
76
77
  rdoc_options: []
77
78
  require_paths:
78
79
  - lib
@@ -87,8 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
88
  - !ruby/object:Gem::Version
88
89
  version: '0'
89
90
  requirements: []
90
- rubygems_version: 3.4.20
91
- signing_key:
91
+ rubygems_version: 3.6.2
92
92
  specification_version: 4
93
93
  summary: Users can install system packages automatically on "gem install"
94
94
  test_files: []