Skip to content

Commit

Permalink
Support specifying the license file in the pyproject.toml file
Browse files Browse the repository at this point in the history
Signed-off-by: Yngve Mardal Moe <yngve.m.moe@gmail.com>
Signed-off-by: Marie Roald <roald.marie@gmail.com>
Co-authored-by: Yngve Mardal Moe <yngve.m.moe@gmail.com>
  • Loading branch information
MarieRoald and yngvem committed May 21, 2024
1 parent 96734be commit 67575d4
Show file tree
Hide file tree
Showing 32 changed files with 352 additions and 61 deletions.
1 change: 1 addition & 0 deletions changes/1812.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The name of the LICENSE file can now be specified in the PEP621-section of the pyproject.toml file
1 change: 1 addition & 0 deletions src/briefcase/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ def parse_config(self, filename, overrides):
config_file,
platform=self.platform,
output_format=self.output_format,
logger=self.logger,
)

# Create the global config
Expand Down
10 changes: 1 addition & 9 deletions src/briefcase/commands/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,16 +647,8 @@ def migrate_necessary_files(self, project_dir, test_source_dir, module_name):

# Copy license file if not already there
license_file = self.pep621_data.get("license", {}).get("file")
if license_file is not None and Path(license_file).name != "LICENSE":
self.logger.warning(
f"\nLicense file found in '{self.base_path}', but its name is "
f"'{Path(license_file).name}', not 'LICENSE'. Briefcase will create a "
"template 'LICENSE' file, but you might want to consider renaming the "
"existing file."
)
copy2(project_dir / "LICENSE", self.base_path / "LICENSE")

elif not (self.base_path / "LICENSE").exists():
if license_file is None and not (self.base_path / "LICENSE").exists():
self.logger.warning(
f"\nLicense file not found in '{self.base_path}'. "
"Briefcase will create a template 'LICENSE' file."
Expand Down
21 changes: 19 additions & 2 deletions src/briefcase/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def __init__(
project_name,
version,
bundle,
license=None,
url=None,
author=None,
author_email=None,
Expand All @@ -166,6 +167,7 @@ def __init__(
self.url = url
self.author = author
self.author_email = author_email
self.license = license

# Version number is PEP440 compliant:
if not is_pep440_canonical_version(self.version):
Expand All @@ -187,6 +189,7 @@ def __init__(
bundle,
description,
sources,
license,
formal_name=None,
url=None,
author=None,
Expand Down Expand Up @@ -228,6 +231,7 @@ def __init__(
self.test_requires = test_requires
self.supported = supported
self.long_description = long_description
self.license = license

if not is_valid_app_name(self.app_name):
raise BriefcaseConfigError(
Expand Down Expand Up @@ -393,7 +397,7 @@ def maybe_update(field, *project_fields):

# Keys that map directly
maybe_update("description", "description")
maybe_update("license", "license", "text")
maybe_update("license", "license")
maybe_update("url", "urls", "Homepage")
maybe_update("version", "version")

Expand Down Expand Up @@ -426,7 +430,7 @@ def maybe_update(field, *project_fields):
pass


def parse_config(config_file, platform, output_format):
def parse_config(config_file, platform, output_format, logger):
"""Parse the briefcase section of the pyproject.toml configuration file.
This method only does basic structural parsing of the TOML, looking for,
Expand Down Expand Up @@ -551,4 +555,17 @@ def parse_config(config_file, platform, output_format):
# of configurations that are being handled.
app_configs[app_name] = config

old_license_format = False
for config in [global_config, *app_configs.values()]:
if isinstance(config.get("license"), str):
config["license"] = {"file": "LICENSE"}
old_license_format = True
if old_license_format:
logger.warning(
"The license was specified using a string, however Briefcase prefers a PEP621 "
"compatible dictionary on the form {'file': '{filename}'} or {'text': '{license_text}'}.\n"
"Therefore, when given the license as a string, Briefcase ignores the license string and "
"defaults to {'file': 'LICENSE'}."
)

return global_config, app_configs
29 changes: 24 additions & 5 deletions src/briefcase/platforms/linux/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,9 +704,28 @@ def build_app(self, app: AppConfig, **kwargs):
doc_folder.mkdir(parents=True, exist_ok=True)

with self.input.wait_bar("Installing license..."):
license_file = self.base_path / "LICENSE"
if license_file.is_file():
self.tools.shutil.copy(license_file, doc_folder / "copyright")
if license_file := app.license.get("file"):
license_file = self.base_path / license_file
if license_file.is_file():
self.tools.shutil.copy(license_file, doc_folder / "copyright")
else:
raise BriefcaseCommandError(
f"""\
You specified that the license file is {license_file}. However, this file does not exist.
Make sure you spelled the name of the license file correctly in `pyproject.toml`.
"""
)
elif license_text := app.license.get("text"):
(doc_folder / "copyright").write_text(license_text, encoding="utf-8")
if len(license_text.splitlines()) <= 1:
self.logger.warning(
"""
You specified the license using a text string. However, this string is only one line.
Make sure that the license text is a full license (or specify a license file).
"""
)
else:
raise BriefcaseCommandError(
"""\
Expand Down Expand Up @@ -1037,7 +1056,7 @@ def _package_rpm(self, app: AppConfig, **kwargs): # pragma: no-cover-if-is-wind
f"Release: {getattr(app, 'revision', 1)}%{{?dist}}",
f"Summary: {app.description}",
"",
f"License: {getattr(app, 'license', 'Unknown')}",
"License: Unknown", # TODO: Add license information (see #1829)
f"URL: {app.url}",
"Source0: %{name}-%{version}.tar.gz",
"",
Expand Down Expand Up @@ -1196,7 +1215,7 @@ def _package_pkg(self, app: AppConfig, **kwargs): # pragma: no-cover-if-is-wind
f'pkgdesc="{app.description}"',
f"arch=('{self.pkg_abi(app)}')",
f'url="{app.url}"',
f"license=('{app.license}')",
"license=('Unknown')",
f"depends=({system_runtime_requires})",
"changelog=CHANGELOG",
'source=("$pkgname-$pkgver.tar.gz")',
Expand Down
1 change: 1 addition & 0 deletions tests/commands/base/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,5 @@ def my_app():
version="1.2.3",
description="This is a simple app",
sources=["src/my_app"],
license={"file": "LICENSE"},
)
2 changes: 2 additions & 0 deletions tests/commands/base/test_finalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def first_app():
version="0.0.1",
description="The first simple app",
sources=["src/first"],
license={"file": "LICENSE"},
)


Expand All @@ -24,6 +25,7 @@ def second_app():
version="0.0.2",
description="The second simple app",
sources=["src/second"],
license={"file": "LICENSE"},
)


Expand Down
5 changes: 5 additions & 0 deletions tests/commands/base/test_parse_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def test_incomplete_global_config(base_command):
[tool.briefcase]
version = "1.2.3"
description = "A sample app"
license.file = "LICENSE"
[tool.briefcase.app.my-app]
""",
Expand All @@ -47,6 +48,7 @@ def test_incomplete_config(base_command):
version = "1.2.3"
bundle = "com.example"
description = "A sample app"
license.file = "LICENSE"
[tool.briefcase.app.my-app]
""",
Expand All @@ -71,6 +73,7 @@ def test_parse_config(base_command):
description = "A sample app"
bundle = "org.beeware"
mystery = 'default'
license.file = "LICENSE"
[tool.briefcase.app.firstapp]
sources = ['src/firstapp']
Expand Down Expand Up @@ -127,6 +130,7 @@ def test_parse_config_with_overrides(base_command):
description = "A sample app"
bundle = "org.beeware"
mystery = 'default'
license.file = "LICENSE"
[tool.briefcase.app.firstapp]
sources = ['src/firstapp']
Expand Down Expand Up @@ -197,6 +201,7 @@ def test_parse_config_with_invalid_override(base_command):
description = "A sample app"
bundle = "org.beeware"
mystery = 'default'
license.file = "LICENSE"
[tool.briefcase.app.firstapp]
sources = ['src/firstapp']
Expand Down
1 change: 1 addition & 0 deletions tests/commands/build/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def second_app_config():
version="0.0.2",
description="The second simple app",
sources=["src/second"],
license={"file": "LICENSE"},
)


Expand Down
33 changes: 26 additions & 7 deletions tests/commands/convert/test_migrate_necessary_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,31 @@ def test_warning_without_license_file(


@pytest.mark.parametrize("test_source_dir", ["tests"])
def test_pep621_wrong_license_filename(
def test_file_is_copied_if_no_license_file_specified(
convert_command,
project_dir_with_files,
dummy_app_name,
test_source_dir,
):
"""A license file is copied if no license file is specified in pyproject.toml."""
create_file(convert_command.base_path / "CHANGELOG", "")
assert not (convert_command.base_path / "LICENSE").exists()
convert_command.migrate_necessary_files(
project_dir_with_files,
test_source_dir,
dummy_app_name,
)
assert (convert_command.base_path / "LICENSE").exists()


@pytest.mark.parametrize("test_source_dir", ["tests"])
def test_pep621_specified_license_filename(
convert_command,
project_dir_with_files,
dummy_app_name,
test_source_dir,
):
"""No license file is copied if a license file is specified in pyproject.toml."""
convert_command.logger.warning = mock.MagicMock()
license_name = "LICENSE.txt"
create_file(convert_command.base_path / license_name, "")
Expand All @@ -121,11 +140,7 @@ def test_pep621_wrong_license_filename(
test_source_dir,
dummy_app_name,
)
convert_command.logger.warning.assert_called_once_with(
f"\nLicense file found in '{convert_command.base_path}', but its name is "
f"'{license_name}', not 'LICENSE'. Briefcase will create a template 'LICENSE' "
"file, but you might want to consider renaming the existing file."
)
assert not (convert_command.base_path / "LICENSE").exists()


@pytest.mark.parametrize("test_source_dir", ["tests"])
Expand Down Expand Up @@ -162,7 +177,11 @@ def test_no_warning_with_license_and_changelog_file(
"""No warning is raised if both license file and changelog file is present."""
convert_command.logger.warning = mock.MagicMock()

create_file(convert_command.base_path / "LICENSE", "")
create_file(
convert_command.base_path / "pyproject.toml",
'[project]\nlicense = { file = "LICENSE.txt" }',
)
create_file(convert_command.base_path / "LICENSE.txt", "")
create_file(convert_command.base_path / "CHANGELOG", "")
convert_command.migrate_necessary_files(
project_dir_with_files,
Expand Down
3 changes: 3 additions & 0 deletions tests/commands/create/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,15 @@ def tracking_create_command(tmp_path, mock_git, monkeypatch_tool_host_os):
version="0.0.1",
description="The first simple app",
sources=["src/first"],
license={"file": "LICENSE"},
),
"second": AppConfig(
app_name="second",
bundle="com.example",
version="0.0.2",
description="The second simple app",
sources=["src/second"],
license={"file": "LICENSE"},
),
},
)
Expand All @@ -223,6 +225,7 @@ def myapp():
url="https://example.com",
author="First Last",
author_email="first@example.com",
license={"file": "LICENSE"},
)


Expand Down
1 change: 1 addition & 0 deletions tests/commands/create/test_create_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def test_create_app_not_supported(tracking_create_command, tmp_path):
description="The third simple app",
sources=["src/third"],
supported=False,
license={"file": "LICENSE"},
)
)

Expand Down
1 change: 1 addition & 0 deletions tests/commands/create/test_generate_app_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def full_context():
"custom_permissions": {},
"requests": {},
"document_types": {},
"license": {"file": "LICENSE"},
# Properties of the generating environment
"python_version": platform.python_version(),
"host_arch": "gothic",
Expand Down
6 changes: 6 additions & 0 deletions tests/commands/create/test_install_app_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def test_no_resources(create_command):
version="1.2.3",
description="This is a simple app",
sources=["src/my_app"],
license={"file": "LICENSE"},
)

# Prime the path index with no targets
Expand All @@ -37,6 +38,7 @@ def test_icon_target(create_command, tmp_path):
description="This is a simple app",
sources=["src/my_app"],
icon="images/icon",
license={"file": "LICENSE"},
)

# Prime the path index with 2 icon targets
Expand Down Expand Up @@ -106,6 +108,7 @@ def test_icon_variant_target(create_command, tmp_path):
"round": "images/round",
"square": "images/square",
},
license={"file": "LICENSE"},
)

# Prime the path index with 2 icon targets
Expand Down Expand Up @@ -190,6 +193,7 @@ def test_splash_target(create_command, capsys):
description="This is a simple app",
sources=["src/my_app"],
splash="images/splash",
license={"file": "LICENSE"},
)

# Prime an empty path index
Expand Down Expand Up @@ -221,6 +225,7 @@ def test_splash_variant_target(create_command, capsys):
"portrait": "images/portrait",
"landscape": "images/landscape",
},
license={"file": "LICENSE"},
)

# Prime an empty path index
Expand Down Expand Up @@ -254,6 +259,7 @@ def test_doctype_icon_target(create_command, tmp_path):
"icon": "images/other-icon",
},
},
license={"file": "LICENSE"},
)

# Prime the path index with 2 document types;
Expand Down
3 changes: 3 additions & 0 deletions tests/commands/dev/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def first_app_uninstalled(tmp_path):
version="0.0.1",
description="The first simple app",
sources=["src/first"],
license={"file": "LICENSE"},
)


Expand Down Expand Up @@ -55,6 +56,7 @@ def second_app(tmp_path):
version="0.0.2",
description="The second simple app",
sources=["src/second"],
license={"file": "LICENSE"},
)


Expand All @@ -75,4 +77,5 @@ def third_app(tmp_path):
description="The third simple app",
sources=["src/third", "src/common", "other"],
test_sources=["tests", "path/to/other"],
license={"file": "LICENSE"},
)
Loading

0 comments on commit 67575d4

Please sign in to comment.