Compare commits

...

3 Commits

Author SHA1 Message Date
Jordan Sissel 43a8e15924 If the action runs with debug logging, run fpm specs with debug logging enabled 2025-10-01 21:59:00 -07:00
Jordan Sissel 13e7ddf864 Python pyproject.toml and requirements.txt improvements
* --python-obey-requirements-txt behavior and tests. I swear this was
  working previously but the tests failed.
* Now supports packaging local python project directories containing
  a pyproject.toml
2025-10-01 21:42:35 -07:00
Jordan Sissel ad8bb033c8 Remove unused code and comments (fpm uses python to process dependency env markers) 2025-10-01 21:27:41 -07:00
4 changed files with 56 additions and 63 deletions

View File

@ -21,6 +21,10 @@ jobs:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- run: |
bundle exec rspec
if [ ! -z "$RUNNER_DEBUG" ] ; then
DEBUG=1 bundle exec rspec -fd
else
bundle exec rspec
fi
env:
SHELL: /usr/bin/bash

View File

@ -98,22 +98,6 @@ class FPM::Package::Python < FPM::Package
:attribute_name => :python_internal_pip,
:default => true
# Environment markers which are known but not yet supported by fpm.
# For some of these markers, it's not even clear if they are useful to fpm's packaging step.
# https://packaging.python.org/en/latest/specifications/dependency-specifiers/#dependency-specifiers
#
# XXX: python's setuptools.pkg_resources can help parse and evaluate such things, even if that library is deprecated:
# >>> f = open("spec/fixtures/python/requirements.txt"); a = pkg_resources.parse_requirements(f.read()); f.close();
# >>> [x for x in list(a) if x.marker is None or x.marker.evaluate()]
# [Requirement.parse('rtxt-dep1>0.1'), Requirement.parse('rtxt-dep2==0.1'), Requirement.parse('rtxt-dep4; python_version > "2.0"')]
#
# Another example, showing only requirements which have environment markers which evaluate to true (or have no markers)
# python3 -c 'import pkg_resources; import json;import sys; r = pkg_resources.parse_requirements(sys.stdin); deps = [d for d in list(r) if d.marker is None or d.marker.evaluate()]; pr
# ["rtxt-dep1>0.1", "rtxt-dep2==0.1", "rtxt-dep4; python_version > \"2.0\""]
UNSUPPORTED_DEPENDENCY_MARKERS = %w(python_version python_full_version os_name platform_release platform_system platform_version
platform_machine platform_python_implementation implementation_name implementation_version)
class PythonMetadata
require "strscan"
@ -328,32 +312,24 @@ class FPM::Package::Python < FPM::Package
# The 'package' can be any of:
#
# * A name of a package on pypi (ie; easy_install some-package)
# * The path to a directory containing setup.py or pypackage.toml
# * The path to a setup.py or pypackage.toml
# * The path to a directory containing setup.py or pyproject.toml
# * The path to a setup.py or pyproject.toml
# * The path to a python sdist file ending in .tar.gz
# * The path to a python wheel file ending in .whl
def input(package)
#if attributes[:python_obey_requirements_txt?]
#raise "--python-obey-requirements-txt is temporarily unsupported at this time."
#end
explore_environment
path_to_package = download_if_necessary(package, version)
# Expect a setup.py or pypackage.toml if it's a directory.
# Expect a setup.py or pyproject.toml if it's a directory.
if File.directory?(path_to_package)
if !(File.exist?(File.join(path_to_package, "setup.py")) or File.exist?(File.join(path_to_package, "pypackage.toml")))
logger.error("The path doesn't appear to be a python package directory. I expected either a pypackage.toml or setup.py but found neither.", :package => package)
raise "Unable to find python package; tried #{setup_py}"
end
if attributes[:python_obey_requirements_txt?] && File.exist?(File.join(path_to_package, "requirements.txt"))
@requirements_txt = File.read(File.join(path_to_package, "requirements.txt"))
if !(File.exist?(File.join(path_to_package, "setup.py")) or File.exist?(File.join(path_to_package, "pyproject.toml")))
raise FPM::InvalidPackageConfiguration, "The path ('#{path_to_package}') doesn't appear to be a python package directory. I expected either a pyproject.toml or setup.py but found neither."
end
end
if File.file?(path_to_package)
if ["setup.py", "pypackage.toml"].include?(File.basename(path_to_package))
if ["setup.py", "pyproject.toml"].include?(File.basename(path_to_package))
path_to_package = File.dirname(path_to_package)
end
end
@ -365,18 +341,22 @@ class FPM::Package::Python < FPM::Package
path_to_package = ::Dir.glob(build_path("*.whl")).first
if path_to_package.nil?
log.error("Failed building python package wheel format. This might be a bug in fpm.")
raise "Failed building python package format."
raise FPM::InvalidPackageConfiguration, "Failed building python package format - fpm tried to build a python wheel, but didn't find the .whl file. This might be a bug in fpm."
end
elsif File.directory?(path_to_package)
logger.debug("Found directory and assuming it's a python source package.")
safesystem(*attributes[:python_pip], "wheel", "--no-deps", "-w", build_path, path_to_package)
if attributes[:python_obey_requirements_txt?]
reqtxt = File.join(path_to_package, "requirements.txt")
@requirements_txt = File.read(reqtxt).split("\n") if File.file?(reqtxt)
end
path_to_package = ::Dir.glob(build_path("*.whl")).first
if path_to_package.nil?
log.error("Failed building python package wheel format. This might be a bug in fpm.")
raise "Failed building python package format."
raise FPM::InvalidPackageConfiguration, "Failed building python package format - fpm tried to build a python wheel, but didn't find the .whl file. This might be a bug in fpm."
end
end
load_package_info(path_to_package)
@ -418,6 +398,11 @@ class FPM::Package::Python < FPM::Package
# If it's a path, assume local build.
if File.exist?(path)
return path if File.directory?(path)
basename = File.basename(path)
return File.dirname(path) if basename == "pyproject.toml"
return File.dirname(path) if basename == "setup.py"
return path if path.end_with?(".tar.gz")
return path if path.end_with?(".tgz") # amqplib v1.0.2 does this
return path if path.end_with?(".whl")
@ -540,9 +525,10 @@ class FPM::Package::Python < FPM::Package
reqs = []
# --python-obey-requirements-txt should replace the requirments listed from the metadata
# --python-obey-requirements-txt should use requirements.txt
# (if found in the python package) and replace the requirments listed from the metadata
if attributes[:python_obey_requirements_txt?] && !@requirements_txt.nil?
requires = @requirements_txt.split("\n")
requires = @requirements_txt
else
requires = metadata.requires
end

View File

@ -0,0 +1,4 @@
[project]
name = "example"
version = "1.2.3"
authors = [ { name = "Captain Fancy", email = "foo@example.com" } ]

View File

@ -208,32 +208,6 @@ describe FPM::Package::Python do
end
end
context "python_scripts_executable is set" do
it "should have scripts with a custom hashbang line" do
skip("setup.py-specific feature is no longer supported")
subject.attributes[:python_scripts_executable] = "fancypants"
# Newer versions of Django require Python 3.
subject.attributes[:python_bin] = "python3"
subject.input("django")
# Determine, where 'easy_install' is going to install scripts
#script_dir = easy_install_default(subject.attributes[:python_bin], 'script_dir')
#path = subject.staging_path(File.join(script_dir, "django-admin.py"))
# Hardcode /usr/local/bin here. On newer Python 3's I cannot figure out how to
# determine the script_dir at installation time. easy_install's method is gone.
path = subject.staging_path("/usr/local/bin/django-admin")
# Read the first line (the hashbang line) of the django-admin.py script
fd = File.new(path, "r")
topline = fd.readline
fd.close
insist { topline.chomp } == "#!fancypants"
end
end
context "when input is a name" do
it "should download from pypi" do
subject.input("click==8.3.0")
@ -247,6 +221,31 @@ describe FPM::Package::Python do
end
end
context "when given a project containing a pyproject.toml" do
let (:project) do
File.expand_path("../../fixtures/python-pyproject.toml/", File.dirname(__FILE__))
end
it "should package it correctly" do
subject.input(project)
prefix = subject.attributes[:python_package_name_prefix]
insist { subject.name } == "#{prefix}-example"
insist { subject.version } == "1.2.3"
insist { subject.maintainer } == "Captain Fancy <foo@example.com>"
end
it "should package it correctly even if the path given is directly to the pyproject.toml" do
subject.input(File.join(project, "pyproject.toml"))
prefix = subject.attributes[:python_package_name_prefix]
insist { subject.name } == "#{prefix}-example"
insist { subject.version } == "1.2.3"
insist { subject.maintainer } == "Captain Fancy <foo@example.com>"
end
end
end # describe FPM::Package::Python
describe FPM::Package::Python::PythonMetadata do