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

Update setup cfg #3423

Merged
merged 24 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ vendor
**/bin/helper
/.core-bash_history
coverage/
.ruby-gemset
.ruby-version
118 changes: 78 additions & 40 deletions python/helpers/lib/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,38 +57,48 @@ def version_from_install_req(install_req):


def parse_setup(directory):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I black formatted this function, so it is actually not so much change.

# Parse the setup.py
setup_packages = []
if os.path.isfile(directory + '/setup.py'):
def version_from_install_req(install_req):
if install_req.is_pinned:
return next(iter(install_req.specifier)).version
def version_from_install_req(install_req):
if install_req.is_pinned:
return next(iter(install_req.specifier)).version

def parse_requirement(req, req_type):
install_req = install_req_from_line(req)
if install_req.original_link:
return
def parse_requirement(req, req_type, filename):
install_req = install_req_from_line(req)
if install_req.original_link:
return

setup_packages.append({
setup_packages.append(
{
"name": install_req.req.name,
"version": version_from_install_req(install_req),
"markers": str(install_req.markers) or None,
"file": "setup.py",
"file": filename,
"requirement": str(install_req.specifier) or None,
"requirement_type": req_type,
"extras": sorted(list(install_req.extras))
})
"extras": sorted(list(install_req.extras)),
}
)

def parse_requirements(requires, req_type, filename):
for req in requires:
parse_requirement(req, req_type, filename)

# Parse the setup.py and setup.cfg
setup_py = "setup.py"
setup_cfg = "setup.cfg"
setup_packages = []

if os.path.isfile(os.path.join(directory, setup_py)):

def setup(*args, **kwargs):
for arg in ['setup_requires', 'install_requires', 'tests_require']:
if not kwargs.get(arg):
continue
for req in kwargs.get(arg):
parse_requirement(req, arg)
extras_require_dict = kwargs.get('extras_require', {})
for key in extras_require_dict:
for req in extras_require_dict[key]:
parse_requirement(req, 'extras_require:{}'.format(key))
for arg in ["setup_requires", "install_requires", "tests_require"]:
requires = kwargs.get(arg, [])
parse_requirements(requires, arg, setup_py)
extras_require_dict = kwargs.get("extras_require", {})
for key, value in extras_require_dict.items():
parse_requirements(
value, "extras_require:{}".format(key), setup_py
)

setuptools.setup = setup

def noop(*args, **kwargs):
Expand All @@ -100,17 +110,19 @@ def fake_parse(*args, **kwargs):
global fake_open

def fake_open(*args, **kwargs):
content = ("VERSION = ('0', '0', '1+dependabot')\n"
"__version__ = '0.0.1+dependabot'\n"
"__author__ = 'someone'\n"
"__title__ = 'something'\n"
"__description__ = 'something'\n"
"__author_email__ = 'something'\n"
"__license__ = 'something'\n"
"__url__ = 'something'\n")
content = (
"VERSION = ('0', '0', '1+dependabot')\n"
"__version__ = '0.0.1+dependabot'\n"
"__author__ = 'someone'\n"
"__title__ = 'something'\n"
"__description__ = 'something'\n"
"__author_email__ = 'something'\n"
"__license__ = 'something'\n"
"__url__ = 'something'\n"
)
return io.StringIO(content)

content = open(directory + '/setup.py', 'r').read()
content = open(directory + "/setup.py", "r").read()

# Remove `print`, `open`, `log` and import statements
content = re.sub(r"print\s*\(", "noop(", content)
Expand All @@ -121,18 +133,44 @@ def fake_open(*args, **kwargs):
content = re.sub(version_re, "", content)

# Set variables likely to be imported
__version__ = '0.0.1+dependabot'
__author__ = 'someone'
__title__ = 'something'
__description__ = 'something'
__author_email__ = 'something'
__license__ = 'something'
__url__ = 'something'
__version__ = "0.0.1+dependabot"
__author__ = "someone"
__title__ = "something"
__description__ = "something"
__author_email__ = "something"
__license__ = "something"
__url__ = "something"

# Run as main (since setup.py is a script)
__name__ = '__main__'
__name__ = "__main__"

# Exec the setup.py
exec(content) in globals(), locals()

if os.path.isfile(os.path.join(directory, setup_cfg)):
try:
config = setuptools.config.read_configuration(
os.path.join(directory, setup_cfg)
)

for req_type in [
"setup_requires",
"install_requires",
"tests_require",
]:
requires = config.get("options", {}).get(req_type, [])
parse_requirements(requires, req_type, setup_cfg)

extras_require = config.get("options", {}).get(
"extras_require", {}
)
for key, value in extras_require.items():
parse_requirements(
value, "extras_require:{}".format(key), setup_cfg
)
except Exception as e:
print(json.dumps({"error": repr(e)}))
exit(1)

return json.dumps({"result": setup_packages})

2 changes: 1 addition & 1 deletion python/helpers/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

if args["function"] == "parse_requirements":
print(parser.parse_requirements(args["args"][0]))
if args["function"] == "parse_setup":
elif args["function"] == "parse_setup":
print(parser.parse_setup(args["args"][0]))
elif args["function"] == "get_dependency_hash":
print(hasher.get_dependency_hash(*args["args"]))
Expand Down
14 changes: 8 additions & 6 deletions python/lib/dependabot/python/file_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ def self.required_files_in?(filenames)
# If this repo is using Poetry return true
return true if filenames.include?("pyproject.toml")

filenames.include?("setup.py")
return true if filenames.include?("setup.py")

filenames.include?("setup.cfg")
end

def self.required_files_message
"Repo must contain a requirements.txt, setup.py, pyproject.toml, "\
"Repo must contain a requirements.txt, setup.py, setup.cfg, pyproject.toml, "\
"or a Pipfile."
end

Expand All @@ -45,7 +47,7 @@ def fetch_files
fetched_files += requirement_files if requirements_txt_files.any?

fetched_files << setup_file if setup_file
fetched_files << setup_cfg if setup_cfg
fetched_files << setup_cfg_file if setup_cfg_file
fetched_files += path_setup_files
fetched_files << pip_conf if pip_conf
fetched_files << python_version if python_version
Expand Down Expand Up @@ -77,7 +79,7 @@ def requirement_files
end

def check_required_files_present
return if requirements_txt_files.any? || setup_file || pipfile || pyproject
return if requirements_txt_files.any? || setup_file || setup_cfg_file || pipfile || pyproject

path = Pathname.new(File.join(directory, "requirements.txt")).
cleanpath.to_path
Expand All @@ -88,8 +90,8 @@ def setup_file
@setup_file ||= fetch_file_if_present("setup.py")
end

def setup_cfg
@setup_cfg ||= fetch_file_if_present("setup.cfg")
def setup_cfg_file
@setup_cfg_file ||= fetch_file_if_present("setup.cfg")
end

def pip_conf
Expand Down
9 changes: 7 additions & 2 deletions python/lib/dependabot/python/file_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def parse
dependency_set += pipenv_dependencies if pipfile
dependency_set += poetry_dependencies if using_poetry?
dependency_set += requirement_dependencies if requirement_files.any?
dependency_set += setup_file_dependencies if setup_file
dependency_set += setup_file_dependencies if setup_file || setup_cfg_file

dependency_set.dependencies
end
Expand Down Expand Up @@ -207,8 +207,9 @@ def check_required_files
return if pipfile
return if pyproject
return if setup_file
return if setup_cfg_file

raise "No requirements.txt or setup.py!"
raise "Missing required files!"
end

def pipfile
Expand Down Expand Up @@ -248,6 +249,10 @@ def setup_file
@setup_file ||= get_original_file("setup.py")
end

def setup_cfg_file
@setup_cfg_file ||= get_original_file("setup.cfg")
end

def pip_compile_files
@pip_compile_files ||=
dependency_files.select { |f| f.name.end_with?(".in") }
Expand Down
17 changes: 17 additions & 0 deletions python/lib/dependabot/python/file_parser/setup_cfg_file_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

require_relative "setup_file_parser_base"

module Dependabot
module Python
class FileParser
class SetupCfgFileParser < SetupFileParserBase
honnix marked this conversation as resolved.
Show resolved Hide resolved
private

def function
"parse_setup_cfg"
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ def parsed_setup_file
rescue SharedHelpers::HelperSubprocessFailed => e
raise Dependabot::DependencyFileNotEvaluatable, e.message if e.message.start_with?("InstallationError")

return [] unless setup_file

parsed_sanitized_setup_file
honnix marked this conversation as resolved.
Show resolved Hide resolved
end

Expand Down
4 changes: 3 additions & 1 deletion python/lib/dependabot/python/file_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def self.updated_files_regex
/.*\.txt$/,
/.*\.in$/,
/^setup\.py$/,
/^setup\.cfg$/,
/^pyproject\.toml$/,
/^pyproject\.lock$/
]
Expand Down Expand Up @@ -113,8 +114,9 @@ def check_required_files
return if pipfile
return if pyproject
return if get_original_file("setup.py")
return if get_original_file("setup.cfg")

raise "No requirements.txt or setup.py!"
raise "Missing required files!"
end

def pipfile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def initialize(requirements:, update_strategy:, has_lockfile:,
def updated_requirements
requirements.map do |req|
case req[:file]
when "setup.py" then updated_setup_requirement(req)
when /setup\.(?:py|cfg)$/ then updated_setup_requirement(req)
when "pyproject.toml" then updated_pyproject_requirement(req)
when "Pipfile" then updated_pipfile_requirement(req)
when /\.txt$|\.in$/ then updated_requirement(req)
Expand Down
26 changes: 26 additions & 0 deletions python/spec/dependabot/python/file_fetcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
it { is_expected.to eq(true) }
end

context "with only a setup.cfg" do
let(:filenames) { %w(setup.cfg) }
it { is_expected.to eq(true) }
end

context "with only a requirements folder" do
let(:filenames) { %w(requirements) }
it { is_expected.to eq(true) }
Expand Down Expand Up @@ -211,6 +216,27 @@
end
end

context "with only a setup.cfg file" do
let(:repo_contents) do
fixture("github", "contents_python_only_setup_cfg.json")
end
before do
stub_request(:get, url + "setup.cfg?ref=sha").
with(headers: { "Authorization" => "token token" }).
to_return(
status: 200,
body: fixture("github", "setup_cfg_content.json"),
headers: { "content-type" => "application/json" }
)
end

it "fetches the setup.cfg file" do
expect(file_fetcher_instance.files.count).to eq(1)
expect(file_fetcher_instance.files.map(&:name)).
to eq(["setup.cfg"])
end
end

context "with only a Pipfile and Pipfile.lock" do
let(:repo_contents) do
fixture("github", "contents_python_only_pipfile_and_lockfile.json")
Expand Down
Loading