Merge pull request #133624 from talyz/discourse-backports

[21.05] discourse: 2.7.5 -> 2.7.7, plugins and fixes
This commit is contained in:
Kim Lindberger 2021-08-19 15:27:08 +02:00 committed by GitHub
commit a1007637ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 387 additions and 38 deletions

View File

@ -771,7 +771,6 @@ in
"tmp"
"assets/javascripts/plugins"
"public"
"plugins"
"sockets"
];
RuntimeDirectoryMode = 0750;

View File

@ -284,11 +284,22 @@ services.discourse = {
Ruby dependencies are listed in its
<filename>plugin.rb</filename> file as function calls to
<literal>gem</literal>. To construct the corresponding
<filename>Gemfile</filename>, run <command>bundle
<filename>Gemfile</filename> manually, run <command>bundle
init</command>, then add the <literal>gem</literal> lines to it
verbatim.
</para>
<para>
Much of the packaging can be done automatically by the
<filename>nixpkgs/pkgs/servers/web-apps/discourse/update.py</filename>
script - just add the plugin to the <literal>plugins</literal>
list in the <function>update_plugins</function> function and run
the script:
<programlisting language="bash">
./update.py update-plugins
</programlisting>.
</para>
<para>
Some plugins provide <link
linkend="module-services-discourse-site-settings">site

View File

@ -4,7 +4,7 @@
# 3. replying to that message via email.
import ./make-test-python.nix (
{ pkgs, lib, ... }:
{ pkgs, lib, package ? pkgs.discourse, ... }:
let
certs = import ./common/acme/server/snakeoil-certs.nix;
clientDomain = "client.fake.domain";
@ -55,7 +55,7 @@ import ./make-test-python.nix (
services.discourse = {
enable = true;
inherit admin;
inherit admin package;
hostname = discourseDomain;
sslCertificate = "${certs.${discourseDomain}.cert}";
sslCertificateKey = "${certs.${discourseDomain}.key}";

View File

@ -0,0 +1,13 @@
diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb
index 380a63e987..b2ce7fa982 100644
--- a/lib/plugin/instance.rb
+++ b/lib/plugin/instance.rb
@@ -403,7 +403,7 @@ class Plugin::Instance
end
def auto_generated_path
- File.dirname(path) << "/auto_generated"
+ "#{Rails.root}/public/assets/auto_generated_plugin_assets/#{name}"
end
def after_initialize(&block)

View File

@ -1,4 +1,4 @@
{ stdenv, makeWrapper, runCommandNoCC, lib, nixosTests, writeShellScript
{ stdenv, pkgs, makeWrapper, runCommandNoCC, lib, writeShellScript
, fetchFromGitHub, bundlerEnv, callPackage
, ruby, replace, gzip, gnutar, git, cacert, util-linux, gawk
@ -6,16 +6,16 @@
, redis, postgresql, which, brotli, procps, rsync, nodePackages, v8
, plugins ? []
}:
}@args:
let
version = "2.7.5";
version = "2.7.7";
src = fetchFromGitHub {
owner = "discourse";
repo = "discourse";
rev = "v${version}";
sha256 = "sha256-OykWaiBAHcZy41i+aRzBHCRgwnfQUBijHjb+ofIk25M=";
sha256 = "sha256-rhcTQyirgPX0ITjgotJAYLLSU957GanxAYYhy9j123U=";
};
runtimeDeps = [
@ -55,6 +55,7 @@ let
, version ? null
, meta ? null
, bundlerEnvArgs ? {}
, preserveGemsDir ? false
, src
, ...
}@args:
@ -65,16 +66,26 @@ let
in
stdenv.mkDerivation (builtins.removeAttrs args [ "bundlerEnvArgs" ] // {
pluginName = if name != null then name else "${pname}-${version}";
phases = [ "unpackPhase" "installPhase" ];
dontConfigure = true;
dontBuild = true;
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r * $out/
'' + lib.optionalString (bundlerEnvArgs != {}) ''
ln -sf ${rubyEnv}/lib/ruby/gems $out/gems
'' + ''
'' + lib.optionalString (bundlerEnvArgs != {}) (
if preserveGemsDir then ''
cp -r ${rubyEnv}/lib/ruby/gems/* $out/gems/
''
else ''
if [[ -e $out/gems ]]; then
echo "Warning: The repo contains a 'gems' directory which will be removed!"
echo " If you need to preserve it, set 'preserveGemsDir = true'."
rm -r $out/gems
fi
ln -sf ${rubyEnv}/lib/ruby/gems $out/gems
'' + ''
runHook postInstall
'';
'');
});
rake = runCommandNoCC "discourse-rake" {
@ -156,6 +167,11 @@ let
# Use the Ruby API version in the plugin gem path, to match the
# one constructed by bundlerEnv
./plugin_gem_api_version.patch
# Change the path to the auto generated plugin assets, which
# defaults to the plugin's directory and isn't writable at the
# time of asset generation
./auto_generated_path.patch
];
# We have to set up an environment that is close enough to
@ -242,6 +258,11 @@ let
# Use mv instead of rename, since rename doesn't work across
# device boundaries
./use_mv_instead_of_rename.patch
# Change the path to the auto generated plugin assets, which
# defaults to the plugin's directory and isn't writable at the
# time of asset generation
./auto_generated_path.patch
];
postPatch = ''
@ -272,7 +293,6 @@ let
ln -sf /run/discourse/config $out/share/discourse/config
ln -sf /run/discourse/assets/javascripts/plugins $out/share/discourse/app/assets/javascripts/plugins
ln -sf /run/discourse/public $out/share/discourse/public
ln -sf /run/discourse/plugins $out/share/discourse/plugins
ln -sf ${assets} $out/share/discourse/public.dist/assets
${lib.concatMapStringsSep "\n" (p: "ln -sf ${p} $out/share/discourse/plugins/${p.pluginName or ""}") plugins}
@ -292,7 +312,7 @@ let
enabledPlugins = plugins;
plugins = callPackage ./plugins/all-plugins.nix { inherit mkDiscoursePlugin; };
ruby = rubyEnv.wrappedRuby;
tests = nixosTests.discourse;
tests = import ../../../../nixos/tests/discourse.nix { package = pkgs.discourse.override args; };
};
};
in discourse

View File

@ -3,9 +3,13 @@ let
callPackage = newScope args;
in
{
discourse-calendar = callPackage ./discourse-calendar {};
discourse-canned-replies = callPackage ./discourse-canned-replies {};
discourse-checklist = callPackage ./discourse-checklist {};
discourse-data-explorer = callPackage ./discourse-data-explorer {};
discourse-github = callPackage ./discourse-github {};
discourse-math = callPackage ./discourse-math {};
discourse-migratepassword = callPackage ./discourse-migratepassword {};
discourse-solved = callPackage ./discourse-solved {};
discourse-spoiler-alert = callPackage ./discourse-spoiler-alert {};
discourse-yearly-review = callPackage ./discourse-yearly-review {};

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
# gem "rails"
gem 'rrule', '0.4.2', require: false

View File

@ -0,0 +1,27 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (6.1.4)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
concurrent-ruby (1.1.9)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
minitest (5.14.4)
rrule (0.4.2)
activesupport (>= 4.1)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
zeitwerk (2.4.2)
PLATFORMS
ruby
DEPENDENCIES
rrule (= 0.4.2)
BUNDLED WITH
2.2.20

View File

@ -0,0 +1,18 @@
{ lib, mkDiscoursePlugin, fetchFromGitHub }:
mkDiscoursePlugin {
name = "discourse-calendar";
bundlerEnvArgs.gemdir = ./.;
src = fetchFromGitHub {
owner = "discourse";
repo = "discourse-calendar";
rev = "519cf403ae3003291de20145aca243e2ffbcb4a2";
sha256 = "0398cf7k03i7j7v5w1mysjzk2npbkvr7icj5sjwa8i8xzg34gck4";
};
meta = with lib; {
homepage = "https://github.com/discourse/discourse-calendar";
maintainers = with maintainers; [ ryantm ];
license = licenses.mit;
description = "Adds the ability to create a dynamic calendar in the first post of a topic";
};
}

View File

@ -0,0 +1,76 @@
{
activesupport = {
dependencies = ["concurrent-ruby" "i18n" "minitest" "tzinfo" "zeitwerk"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0kqgywy4cj3h5142dh7pl0xx5nybp25jn0ykk0znziivzks68xdk";
type = "gem";
};
version = "6.1.4";
};
concurrent-ruby = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0nwad3211p7yv9sda31jmbyw6sdafzmdi2i2niaz6f0wk5nq9h0f";
type = "gem";
};
version = "1.1.9";
};
i18n = {
dependencies = ["concurrent-ruby"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0g2fnag935zn2ggm5cn6k4s4xvv53v2givj1j90szmvavlpya96a";
type = "gem";
};
version = "1.8.10";
};
minitest = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "19z7wkhg59y8abginfrm2wzplz7py3va8fyngiigngqvsws6cwgl";
type = "gem";
};
version = "5.14.4";
};
rrule = {
dependencies = ["activesupport"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0w338b7dgvd144fl5b8xy2lfc6zgbcjac7b4z158jc8h070yzc9v";
type = "gem";
};
version = "0.4.2";
};
tzinfo = {
dependencies = ["concurrent-ruby"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "10qp5x7f9hvlc0psv9gsfbxg4a7s0485wsbq1kljkxq94in91l4z";
type = "gem";
};
version = "2.0.4";
};
zeitwerk = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "1746czsjarixq0x05f7p3hpzi38ldg6wxnxxw74kbjzh1sdjgmpl";
type = "gem";
};
version = "2.4.2";
};
}

View File

@ -5,8 +5,8 @@ mkDiscoursePlugin {
src = fetchFromGitHub {
owner = "discourse";
repo = "discourse-canned-replies";
rev = "e3f1de8928df5955b64994079b7e2073556e5456";
sha256 = "1g4fazm6cn6hbfd08mq2zhc6dgm4qj1r1f1amhbgxhk6qsxf42cd";
rev = "672a96a8160d3767cf5fd6647309c7b5dcf8a55d";
sha256 = "105zgpc7j3xmlkaz3cgxw1rfgy5d3dzln58ix569jmzifbsijml7";
};
meta = with lib; {
homepage = "https://github.com/discourse/discourse-canned-replies";

View File

@ -0,0 +1,17 @@
{ lib, mkDiscoursePlugin, fetchFromGitHub }:
mkDiscoursePlugin {
name = "discourse-checklist";
src = fetchFromGitHub {
owner = "discourse";
repo = "discourse-checklist";
rev = "6e7b9c5040c55795c7fd4db9569b3e93dad092c2";
sha256 = "sha256-2KAVBrfAvhLZC9idi+ijbVqOCq9rSXbDVEOZS+mWJ10=";
};
meta = with lib; {
homepage = "https://github.com/discourse/discourse-checklist";
maintainers = with maintainers; [ ryantm ];
license = licenses.gpl2Only;
description = "A simple checklist rendering plugin for discourse ";
};
}

View File

@ -0,0 +1,17 @@
{ lib, mkDiscoursePlugin, fetchFromGitHub }:
mkDiscoursePlugin {
name = "discourse-data-explorer";
src = fetchFromGitHub {
owner = "discourse";
repo = "discourse-data-explorer";
rev = "23287ece952cb45203819e7b470ebc194c58cb13";
sha256 = "1vc2072r72fkvcfpy6vpn9x4gl9lpjk29pnj8095xs22im8j5in1";
};
meta = with lib; {
homepage = "https://github.com/discourse/discourse-data-explorer";
maintainers = with maintainers; [ ryantm ];
license = licenses.mit;
description = "SQL Queries for admins in Discourse";
};
}

View File

@ -3,7 +3,7 @@ GEM
specs:
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
faraday (1.5.0)
faraday (1.7.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@ -11,6 +11,7 @@ GEM
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
@ -18,20 +19,21 @@ GEM
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.1.0)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
multipart-post (2.1.1)
octokit (4.21.0)
faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
public_suffix (4.0.6)
ruby2_keywords (0.0.4)
ruby2_keywords (0.0.5)
sawyer (0.8.2)
addressable (>= 2.3.5)
faraday (> 0.8, < 2.0)
PLATFORMS
x86_64-linux
ruby
DEPENDENCIES
octokit (= 4.21.0)

View File

@ -6,8 +6,8 @@ mkDiscoursePlugin {
src = fetchFromGitHub {
owner = "discourse";
repo = "discourse-github";
rev = "154fd5ea597640c2259ce489b4ce75b48ac1973c";
sha256 = "0wb5p219z42rc035rnh2iwrbsj000nxa9shbmc325rzcg6xlhdhw";
rev = "b6ad8e39a13e2ad5c6943ea697ca23f2c5f9fec1";
sha256 = "0vxwp4kbf44clcqilb8ni0ykk4jrgiv4rbd05pgfvndcp3izm2i6";
};
meta = with lib; {
homepage = "https://github.com/discourse/discourse-github";

View File

@ -11,15 +11,15 @@
version = "2.8.0";
};
faraday = {
dependencies = ["faraday-em_http" "faraday-em_synchrony" "faraday-excon" "faraday-httpclient" "faraday-net_http" "faraday-net_http_persistent" "faraday-patron" "multipart-post" "ruby2_keywords"];
dependencies = ["faraday-em_http" "faraday-em_synchrony" "faraday-excon" "faraday-httpclient" "faraday-net_http" "faraday-net_http_persistent" "faraday-patron" "faraday-rack" "multipart-post" "ruby2_keywords"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0gwbii45plm9bljk22bwzhzxrc5xid8qx24f54vrm74q3zaz00ah";
sha256 = "0r6ik2yvsbx6jj30vck32da2bbvj4m0gf4jhp09vr75i1d6jzfvb";
type = "gem";
};
version = "1.5.0";
version = "1.7.0";
};
faraday-em_http = {
groups = ["default"];
@ -76,10 +76,10 @@
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0l2c835wl7gv34xp49fhd1bl4czkpw2g3ahqsak2251iqv5589ka";
sha256 = "0dc36ih95qw3rlccffcb0vgxjhmipsvxhn6cw71l7ffs0f7vq30b";
type = "gem";
};
version = "1.1.0";
version = "1.2.0";
};
faraday-patron = {
groups = ["default"];
@ -91,6 +91,16 @@
};
version = "1.0.0";
};
faraday-rack = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "1h184g4vqql5jv9s9im6igy00jp6mrah2h14py6mpf9bkabfqq7g";
type = "gem";
};
version = "1.0.0";
};
multipart-post = {
groups = ["default"];
platforms = [];
@ -127,10 +137,10 @@
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "15wfcqxyfgka05v2a7kpg64x57gl1y4xzvnc9lh60bqx5sf1iqrs";
sha256 = "1vz322p8n39hz3b4a9gkmz9y7a5jaz41zrm2ywf31dvkqm03glgz";
type = "gem";
};
version = "0.0.4";
version = "0.0.5";
};
sawyer = {
dependencies = ["addressable" "faraday"];

View File

@ -0,0 +1,6 @@
# frozen_string_literal: true
source "https://rubygems.org"
gem 'bcrypt', '3.1.3'
gem 'unix-crypt', '1.3.0'

View File

@ -0,0 +1,15 @@
GEM
remote: https://rubygems.org/
specs:
bcrypt (3.1.3)
unix-crypt (1.3.0)
PLATFORMS
x86_64-linux
DEPENDENCIES
bcrypt (= 3.1.3)
unix-crypt (= 1.3.0)
BUNDLED WITH
2.2.20

View File

@ -0,0 +1,18 @@
{ lib, mkDiscoursePlugin, fetchFromGitHub }:
mkDiscoursePlugin {
name = "discourse-migratepassword";
bundlerEnvArgs.gemdir = ./.;
src = fetchFromGitHub {
owner = "communiteq";
repo = "discourse-migratepassword";
rev = "91d6a008de91853becca01846aa4662bd227670e";
sha256 = "sha256-aKj0zXyXDnG20qVdhGvn4fwXiBeHFj2pv4bTUP81MP0=";
};
meta = with lib; {
homepage = "https://github.com/communiteq/discourse-migratepassword";
maintainers = with maintainers; [ ryantm ];
license = licenses.gpl2Only;
description = "Support migrated password hashes";
};
}

View File

@ -0,0 +1,22 @@
{
bcrypt = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "1d2gqv8vry4ps0asb7nn1z4zxi3mcscy7yrim0npdd294ffyinvj";
type = "gem";
};
version = "3.1.3";
};
unix-crypt = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "1wflipsmmicmgvqilp9pml4x19b337kh6p6jgrzqrzpkq2z52gdq";
type = "gem";
};
version = "1.3.0";
};
}

View File

@ -5,8 +5,8 @@ mkDiscoursePlugin {
src = fetchFromGitHub {
owner = "discourse";
repo = "discourse-solved";
rev = "b96374bf4ab7e6d5cecb0761918b060a524eb9bf";
sha256 = "0zrv70p0wz93akpcj6gpwjkw7az3iz9bx4n2z630kyrlmxdbj32a";
rev = "8bf54370200fe9d94541f69339430a7dc1019d62";
sha256 = "1sk91h4dilkxm1wpv8zw59wgw860ywwlcgiw2kd23ybdk9n7b3lh";
};
meta = with lib; {
homepage = "https://github.com/discourse/discourse-solved";

View File

@ -12,6 +12,7 @@ import os
import stat
import json
import requests
import textwrap
from distutils.version import LooseVersion
from pathlib import Path
from typing import Iterable
@ -77,7 +78,11 @@ def _call_nix_update(pkg, version):
def _nix_eval(expr: str):
nixpkgs_path = Path(__file__).parent / '../../../../'
return json.loads(subprocess.check_output(['nix', 'eval', '--json', f'(with import {nixpkgs_path} {{}}; {expr})'], text=True))
try:
output = subprocess.check_output(['nix', 'eval', '--json', f'(with import {nixpkgs_path} {{}}; {expr})'], text=True)
except subprocess.CalledProcessError:
return None
return json.loads(output)
def _get_current_package_version(pkg: str):
@ -111,6 +116,18 @@ def _diff_file(filepath: str, old_version: str, new_version: str):
return
def _remove_platforms(rubyenv_dir: Path):
for platform in ['arm64-darwin-20', 'x86_64-darwin-18',
'x86_64-darwin-19', 'x86_64-darwin-20',
'x86_64-linux']:
with open(rubyenv_dir / 'Gemfile.lock', 'r') as f:
for line in f:
if platform in line:
subprocess.check_output(
['bundle', 'lock', '--remove-platform', platform], cwd=rubyenv_dir)
break
@click_log.simple_verbosity_option(logger)
@ -173,10 +190,7 @@ def update(rev):
f.write(repo.get_file(fn, rev))
subprocess.check_output(['bundle', 'lock'], cwd=rubyenv_dir)
for platform in ['arm64-darwin-20', 'x86_64-darwin-18',
'x86_64-darwin-19', 'x86_64-darwin-20',
'x86_64-linux']:
subprocess.check_output(['bundle', 'lock', '--remove-platform', platform], cwd=rubyenv_dir)
_remove_platforms(rubyenv_dir)
subprocess.check_output(['bundix'], cwd=rubyenv_dir)
_call_nix_update('discourse', repo.rev2version(rev))
@ -187,9 +201,13 @@ def update_plugins():
"""
plugins = [
{'name': 'discourse-calendar'},
{'name': 'discourse-canned-replies'},
{'name': 'discourse-checklist'},
{'name': 'discourse-data-explorer'},
{'name': 'discourse-github'},
{'name': 'discourse-math'},
{'name': 'discourse-migratepassword', 'owner': 'discoursehosting'},
{'name': 'discourse-solved'},
{'name': 'discourse-spoiler-alert'},
{'name': 'discourse-yearly-review'},
@ -202,13 +220,59 @@ def update_plugins():
repo_name = plugin.get('repo_name') or name
repo = DiscourseRepo(owner=owner, repo=repo_name)
filename = _nix_eval(f'builtins.unsafeGetAttrPos "src" discourse.plugins.{name}')
if filename is None:
filename = Path(__file__).parent / 'plugins' / name / 'default.nix'
filename.parent.mkdir()
has_ruby_deps = False
for line in repo.get_file('plugin.rb', repo.latest_commit_sha).splitlines():
if 'gem ' in line:
has_ruby_deps = True
break
with open(filename, 'w') as f:
f.write(textwrap.dedent(f"""
{{ lib, mkDiscoursePlugin, fetchFromGitHub }}:
mkDiscoursePlugin {{
name = "{name}";"""[1:] + ("""
bundlerEnvArgs.gemdir = ./.;""" if has_ruby_deps else "") + f"""
src = {fetcher} {{
owner = "{owner}";
repo = "{repo_name}";
rev = "replace-with-git-rev";
sha256 = "replace-with-sha256";
}};
meta = with lib; {{
homepage = "";
maintainers = with maintainers; [ ];
license = licenses.mit; # change to the correct license!
description = "";
}};
}}"""))
all_plugins_filename = Path(__file__).parent / 'plugins' / 'all-plugins.nix'
with open(all_plugins_filename, 'r+') as f:
content = f.read()
pos = -1
while content[pos] != '}':
pos -= 1
content = content[:pos] + f' {name} = callPackage ./{name} {{}};' + os.linesep + content[pos:]
f.seek(0)
f.write(content)
f.truncate()
else:
filename = filename['file']
prev_commit_sha = _nix_eval(f'discourse.plugins.{name}.src.rev')
if prev_commit_sha == repo.latest_commit_sha:
click.echo(f'Plugin {name} is already at the latest revision')
continue
filename = _nix_eval(f'builtins.unsafeGetAttrPos "src" discourse.plugins.{name}')['file']
prev_hash = _nix_eval(f'discourse.plugins.{name}.src.outputHash')
new_hash = subprocess.check_output([
'nix-universal-prefetch', fetcher,
@ -244,7 +308,9 @@ def update_plugins():
with open(gemfile, 'a') as f:
f.write(gemfile_text)
subprocess.check_output(['bundle', 'lock', '--add-platform', 'ruby'], cwd=rubyenv_dir)
subprocess.check_output(['bundle', 'lock', '--update'], cwd=rubyenv_dir)
_remove_platforms(rubyenv_dir)
subprocess.check_output(['bundix'], cwd=rubyenv_dir)