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

Ensure that each package has a separate list of data elements. #675

Merged
merged 4 commits into from Mar 23, 2022

Conversation

vyasr
Copy link
Contributor

@vyasr vyasr commented Mar 7, 2022

Currently when scikit-build copies files from the source tree to the build tree (files that do not need to be compiled, e.g. pure Python files or Cython headers) it uses the package_data dict as a way to keep track of these files since data files are subject to the same requirements at this stage. skbuild assumes that modifying the package_data dict in place is safe here because it deep copies the package_data parameter passed to setup.py here. However, deep copying is not sufficient in the scenario where the calling code constructs the input dict such that all values are actually references to the same dictionary, e.g.

files = ['foo', 'bar']
...
setup(..., package_data={package: files for package in packages},...)

or

setup(..., package_data=dict.fromkeys(packages, ['foo', 'bar']), ...)

setuptools.setup appears to have no problem with these inputs, so scikit-build should support them as well. The safer approach here is to use a dict comprehension that explicitly copies each list. The lists will not be nested, so this approach is sufficient to guarantee that each list is unique and can be safely modified.

@vyasr vyasr force-pushed the fix/package_data_ref branch from be7afa0 to 1517826 Compare Mar 7, 2022
@jcfr jcfr self-assigned this Mar 10, 2022
@jcfr
Copy link
Contributor

jcfr commented Mar 10, 2022

Additional context:

Commit c3a19ba from 2018 added relevant tests and introduced the use of deepcopy along side with _consolidate_package_data_files:

-    package_data = kw.get('package_data', {}).copy()
+    package_data = copy.deepcopy(kw.get('package_data', {}))

Source: c3a19ba#diff-31c811f03446fcfe70dbf03560cef0781f582d6f5d66334b76b2282e2e0b8733L386-R388

For comparison, this pull request introduces the following changes:

-    package_data = copy.deepcopy(kw.get("package_data", {}))
+    package_data = {k: copy.copy(v) for k, v in kw.get("package_data", {}).items()}

Source: https://github.com/scikit-build/scikit-build/pull/675/files#diff-31c811f03446fcfe70dbf03560cef0781f582d6f5d66334b76b2282e2e0b8733L513-R513

@vyasr
Copy link
Contributor Author

vyasr commented Mar 10, 2022

To give some context about why these behavior are different:

My guess is that the first change you linked was made to fix an issue where if a user did

data_names = ['bar']
setup(..., package_data = {'foo': data_names}, ...)

and you just called package_data.copy() it would result in a shallow copy of the dict, so modifying package_data['foo'] inside skbuild would lead to changes in the user-provided input list. Perhaps there were also other side effects within skbuild of such a shallow copy. My changes should still be safe in those cases, but they also address the more subtle case where a user does

data_names = ['bar']
setup(..., package_data = {'foo': data_names, 'baz': data_names}, ...)

In this scenario the deepcopy code will result in skbuild using a new object internally, so it will not persist changes to the caller's data_names variable. However, any changes to package_data['foo'] inside skbuild will also propagate to package_data['bar'] because the deepcopy will see that in the original dictionary both values are references to the same list and will therefore propagate that reference structure to the deep copy.

@henryiii
Copy link
Contributor

henryiii commented Mar 22, 2022

The analysis sounds correct to me, and this seems fairly safe. I would guess you could still do a deep copy inside (package_data = {k: copy.deepcopy(v) for k, v in kw.get("package_data", {}).items()}) for a little extra peace of mind, but I think this is fine. It still addresses the reason for the original deep copy, it fixes a more subtle version of the problem still present with deepcopy, and it exactly targets the thing we modify for the copy.

@henryiii henryiii added this to the 0.14.0 milestone Mar 22, 2022
@vyasr
Copy link
Contributor Author

vyasr commented Mar 23, 2022

You could safely do a deepcopy. I think that the API explicitly requires the values to be lists of strings, so anything that required a deep rather than a shallow copy for safety would violate the existing API, but there's probably no harm in future-proofing in case that changes.

@jcfr
Copy link
Contributor

jcfr commented Mar 23, 2022

Thanks @vyasr and @henryiii for following up on this 🙏

jcfr
jcfr approved these changes Mar 23, 2022
@henryiii henryiii merged commit 862f034 into scikit-build:master Mar 23, 2022
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants