Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

language/python: order args for virtualenv_install_with_resources #16817

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 34 additions & 2 deletions Library/Homebrew/language/python.rb
Expand Up @@ -224,10 +224,13 @@ def needs_python?(python)
system_site_packages: T::Boolean,
without_pip: T::Boolean,
link_manpages: T::Boolean,
without: T.nilable(T.any(String, T::Array[String])),
start_with: T.nilable(T.any(String, T::Array[String])),
end_with: T.nilable(T.any(String, T::Array[String])),
).returns(Virtualenv)
}
def virtualenv_install_with_resources(using: nil, system_site_packages: true, without_pip: true,
link_manpages: false)
link_manpages: false, without: nil, start_with: nil, end_with: nil)
python = using
if python.nil?
wanted = python_names.select { |py| needs_python?(py) }
Expand All @@ -237,9 +240,22 @@ def virtualenv_install_with_resources(using: nil, system_site_packages: true, wi
python = T.must(wanted.first)
python = "python3" if python == "python"
end

venv_resources = if without.nil? && start_with.nil? && end_with.nil?
resources
else
remaining_resources = resources.to_h { |resource| [resource.name, resource] }

slice_resources!(remaining_resources, Array(without))
start_with_resources = slice_resources!(remaining_resources, Array(start_with))
end_with_resources = slice_resources!(remaining_resources, Array(end_with))

start_with_resources + remaining_resources.values + end_with_resources
end

venv = virtualenv_create(libexec, python.delete("@"), system_site_packages:,
without_pip:)
venv.pip_install resources
venv.pip_install venv_resources
venv.pip_install_and_link(T.must(buildpath), link_manpages:)
venv
end
Expand All @@ -249,6 +265,22 @@ def python_names
%w[python python3 pypy pypy3] + Formula.names.select { |name| name.start_with? "python@" }
end

private

sig {
params(
resources_hash: T::Hash[String, Resource],
resource_names: T::Array[String],
).returns(T::Array[Resource])
}
def slice_resources!(resources_hash, resource_names)
resource_names.map do |resource_name|
resources_hash.delete(resource_name) do
raise ArgumentError, "Resource \"#{resource_name}\" is not defined in formula or is already used"
end
end
end

# Convenience wrapper for creating and installing packages into Python
# virtualenvs.
class Virtualenv
Expand Down
152 changes: 150 additions & 2 deletions Library/Homebrew/test/language/python/virtualenv_spec.rb
Expand Up @@ -3,7 +3,153 @@
require "language/python"
require "resource"

RSpec.describe Language::Python::Virtualenv::Virtualenv, :needs_python do
RSpec.describe Language::Python::Virtualenv, :needs_python do
describe "#virtualenv_install_with_resources" do
let(:venv) { instance_double(Language::Python::Virtualenv::Virtualenv) }
let(:f) do
formula "foo" do
# Couldn't find a way to get described_class to work inside formula do
# rubocop:disable RSpec/DescribedClass
include Language::Python::Virtualenv
# rubocop:enable RSpec/DescribedClass

url "https://brew.sh/foo-1.0.tgz"

resource "resource-a" do
url "https://brew.sh/resource1.tar.gz"
sha256 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
end

resource "resource-b" do
url "https://brew.sh/resource2.tar.gz"
sha256 "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
end

resource "resource-c" do
url "https://brew.sh/resource3.tar.gz"
sha256 "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
end

resource "resource-d" do
url "https://brew.sh/resource4.tar.gz"
sha256 "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
end
end
end
let(:r_a) { f.resource("resource-a") }
let(:r_b) { f.resource("resource-b") }
let(:r_c) { f.resource("resource-c") }
let(:r_d) { f.resource("resource-d") }
let(:buildpath) { Pathname(TEST_TMPDIR) }

before { f.instance_variable_set(:@buildpath, buildpath) }

it "works with `using: \"python\"` and installs resources in order" do
expect(f).to receive(:virtualenv_create).with(
f.libexec, "python", { system_site_packages: true, without_pip: true }
).and_return(venv)
expect(venv).to receive(:pip_install).with([r_a, r_b, r_c, r_d])
expect(venv).to receive(:pip_install_and_link).with(buildpath, { link_manpages: false })
f.virtualenv_install_with_resources(using: "python")
end

it "works with `using: \"python@3.12\"` and installs resources in order" do
expect(f).to receive(:virtualenv_create).with(
f.libexec, "python3.12", { system_site_packages: true, without_pip: true }
).and_return(venv)
expect(venv).to receive(:pip_install).with([r_a, r_b, r_c, r_d])
expect(venv).to receive(:pip_install_and_link).with(buildpath, { link_manpages: false })
f.virtualenv_install_with_resources(using: "python@3.12")
end

it "skips a `without` resource string and installs remaining resources in order" do
expect(f).to receive(:virtualenv_create).and_return(venv)
expect(venv).to receive(:pip_install).with([r_a, r_b, r_d])
expect(venv).to receive(:pip_install_and_link).with(buildpath, { link_manpages: false })
f.virtualenv_install_with_resources(using: "python", without: r_c.name)
end

it "skips all resources in `without` array and installs remaining resources in order" do
expect(f).to receive(:virtualenv_create).and_return(venv)
expect(venv).to receive(:pip_install).with([r_b, r_c])
expect(venv).to receive(:pip_install_and_link).with(buildpath, { link_manpages: false })
f.virtualenv_install_with_resources(using: "python", without: [r_d.name, r_a.name])
end

it "errors if `without` resource string does not exist in formula" do
expect do
f.virtualenv_install_with_resources(using: "python", without: "unknown")
end.to raise_error(ArgumentError)
end

it "errors if `without` resource array refers to a resource that does not exist in formula" do
expect do
f.virtualenv_install_with_resources(using: "python", without: [r_a.name, "unknown"])
end.to raise_error(ArgumentError)
end

it "installs a `start_with` resource string and then remaining resources in order" do
expect(f).to receive(:virtualenv_create).and_return(venv)
expect(venv).to receive(:pip_install).with([r_c, r_a, r_b, r_d])
expect(venv).to receive(:pip_install_and_link).with(buildpath, { link_manpages: false })
f.virtualenv_install_with_resources(using: "python", start_with: r_c.name)
end

it "installs all resources in `start_with` array and then remaining resources in order" do
expect(f).to receive(:virtualenv_create).and_return(venv)
expect(venv).to receive(:pip_install).with([r_d, r_b, r_a, r_c])
expect(venv).to receive(:pip_install_and_link).with(buildpath, { link_manpages: false })
f.virtualenv_install_with_resources(using: "python", start_with: [r_d.name, r_b.name])
end

it "errors if `start_with` resource string does not exist in formula" do
expect do
f.virtualenv_install_with_resources(using: "python", start_with: "unknown")
end.to raise_error(ArgumentError)
end

it "errors if `start_with` resource array refers to a resource that does not exist in formula" do
expect do
f.virtualenv_install_with_resources(using: "python", start_with: [r_a.name, "unknown"])
end.to raise_error(ArgumentError)
end

it "installs an `end_with` resource string as last resource" do
expect(f).to receive(:virtualenv_create).and_return(venv)
expect(venv).to receive(:pip_install).with([r_a, r_c, r_d, r_b])
expect(venv).to receive(:pip_install_and_link).with(buildpath, { link_manpages: false })
f.virtualenv_install_with_resources(using: "python", end_with: r_b.name)
end

it "installs all resources in `end_with` array after other resources are installed" do
expect(f).to receive(:virtualenv_create).and_return(venv)
expect(venv).to receive(:pip_install).with([r_a, r_d, r_c, r_b])
expect(venv).to receive(:pip_install_and_link).with(buildpath, { link_manpages: false })
f.virtualenv_install_with_resources(using: "python", end_with: [r_c.name, r_b.name])
end

it "errors if `end_with` resource string does not exist in formula" do
expect do
f.virtualenv_install_with_resources(using: "python", end_with: "unknown")
end.to raise_error(ArgumentError)
end

it "errors if `end_with` resource array refers to a resource that does not exist in formula" do
expect do
f.virtualenv_install_with_resources(using: "python", end_with: [r_a.name, "unknown"])
end.to raise_error(ArgumentError)
end

it "installs resources in correct order when combining `without`, `start_with`, and `end_with" do
expect(f).to receive(:virtualenv_create).and_return(venv)
expect(venv).to receive(:pip_install).with([r_d, r_c, r_b])
expect(venv).to receive(:pip_install_and_link).with(buildpath, { link_manpages: false })
f.virtualenv_install_with_resources(using: "python", without: r_a.name,
start_with: r_d.name, end_with: r_b.name)
end
end

describe Language::Python::Virtualenv::Virtualenv do
subject(:virtualenv) { described_class.new(formula, dir, "python") }

let(:dir) { mktmpdir }
Expand All @@ -15,7 +161,8 @@

describe "#create" do
it "creates a venv" do
expect(formula).to receive(:system).with("python", "-m", "venv", "--system-site-packages", "--without-pip", dir)
expect(formula).to receive(:system)
.with("python", "-m", "venv", "--system-site-packages", "--without-pip", dir)
virtualenv.create
end

Expand Down Expand Up @@ -151,3 +298,4 @@
end
end
end
end