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

Copy permissions when merging macOS app packages #1512

Merged
merged 5 commits into from
Oct 26, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/1510.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When merging dependencies on macOS, file permissions are now preserved.
6 changes: 3 additions & 3 deletions src/briefcase/platforms/macOS/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,9 @@ def merge_app_packages(
f"between sources; ignoring {source_app_packages.suffix[1:]} version."
)
else:
# The file doesn't exist yet; copy it as is, and store the
# digest for later comparison
self.tools.shutil.copyfile(source_path, target_path)
# The file doesn't exist yet; copy it as is (including
# permissions), and store the digest for later comparison
self.tools.shutil.copy(source_path, target_path)
digests[relative_path] = sha256_file_digest(source_path)

# Call lipo on each dylib that was found to create the fat version.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import subprocess
from pathlib import Path

Expand Down Expand Up @@ -26,11 +27,13 @@ def test_merge(dummy_command, pre_existing, tmp_path):
extra_content=[
("second/other.py", "# other python"),
("second/different.py", "# different python"),
("second/some-binary", "# A file with executable permissions", 0o755),
("second/sub1/module1.dylib", "dylib-gothic"),
("second/sub1/module2.so", "dylib-gothic"),
("second/sub1/module3.dylib", "dylib-gothic"),
],
)

# Create 2 packages in the "modern" architecture app package sources
# The first package is pure, so it won't exist in the second app_packages.
# The "second" package:
Expand Down Expand Up @@ -110,6 +113,7 @@ def lipo(cmd, **kwargs):
(Path("second/__init__.py"), ""),
(Path("second/app.py"), "# This is the app"),
(Path("second/different.py"), "# different python"),
(Path("second/some-binary"), "# A file with executable permissions"),
(Path("second/other.py"), "# other python"),
(Path("second/sub1"), None),
(Path("second/sub1/module1.dylib"), "dylib-merged"),
Expand Down Expand Up @@ -149,6 +153,9 @@ def lipo(cmd, **kwargs):
),
}

# Check that the embedded binary has executable permissions
assert os.access(merged_path / "second" / "some-binary", os.X_OK)


def test_merge_problem(dummy_command, tmp_path):
"If a binary cannot be merged, an exception is raised."
Expand Down
19 changes: 13 additions & 6 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,16 +235,22 @@ def create_installed_package(
:param package: The name of the package in the wheel. Defaults to ``dummy``
:param version: The version number of the package. Defaults to ``1.2.3``
:param tag: The installation tag for the package. Defaults to a pure python wheel.
:param extra_content: Optional. A list of tuples of ``(path, content)`` that will be
added to the wheel.
:param extra_content: Optional. A list of tuples of ``(path, content)`` or
``(path, content, chmod)`` that will be added to the wheel. If ``chmod`` is
not specified, default filesystem permissions will be used.
"""
for filename, content in installed_package_content(
for entry in installed_package_content(
package=package,
version=version,
tag=tag,
extra_content=extra_content,
):
create_file(path / filename, content=content)
try:
filename, content, chmod = entry
except ValueError:
filename, content = entry
chmod = None
create_file(path / filename, content=content, chmod=chmod)


def create_wheel(
Expand All @@ -260,8 +266,9 @@ def create_wheel(
:param package: The name of the package in the wheel. Defaults to ``dummy``
:param version: The version number of the package. Defaults to ``1.2.3``
:param tag: The installation tag for the package. Defaults to a pure python wheel.
:param extra_content: Optional. A list of tuples of ``(path, content)`` that
will be added to the wheel.
:param extra_content: Optional. A list of tuples of ``(path, content)`` or
``(path, content, chmod)`` that will be added to the wheel. If ``chmod`` is
not specified, default filesystem permissions will be used.
"""
wheel_filename = path / f"{package}-{version}-{tag}.whl"

Expand Down