14
14
15
15
"Set defaults for the pip-compile command to run it under Bazel"
16
16
17
+ import atexit
17
18
import os
18
- import re
19
+ import shutil
19
20
import sys
20
21
from pathlib import Path
21
- from shutil import copyfile
22
22
23
+ import piptools .writer as piptools_writer
23
24
from piptools .scripts .compile import cli
24
25
26
+ # Replace the os.replace function with shutil.copy to work around os.replace not being able to
27
+ # replace or move files across filesystems.
28
+ os .replace = shutil .copy
29
+
30
+ # Next, we override the annotation_style_split and annotation_style_line functions to replace the
31
+ # backslashes in the paths with forward slashes. This is so that we can have the same requirements
32
+ # file on Windows and Unix-like.
33
+ original_annotation_style_split = piptools_writer .annotation_style_split
34
+ original_annotation_style_line = piptools_writer .annotation_style_line
35
+
36
+
37
+ def annotation_style_split (required_by ) -> str :
38
+ required_by = set ([v .replace ("\\ " , "/" ) for v in required_by ])
39
+ return original_annotation_style_split (required_by )
40
+
41
+
42
+ def annotation_style_line (required_by ) -> str :
43
+ required_by = set ([v .replace ("\\ " , "/" ) for v in required_by ])
44
+ return original_annotation_style_line (required_by )
45
+
46
+
47
+ piptools_writer .annotation_style_split = annotation_style_split
48
+ piptools_writer .annotation_style_line = annotation_style_line
49
+
25
50
26
51
def _select_golden_requirements_file (
27
52
requirements_txt , requirements_linux , requirements_darwin , requirements_windows
@@ -41,19 +66,6 @@ def _select_golden_requirements_file(
41
66
return requirements_txt
42
67
43
68
44
- def _fix_up_requirements_in_path (absolute_prefix , output_file ):
45
- """Fix up references to the input file inside of the generated requirements file.
46
-
47
- We don't want fully resolved, absolute paths in the generated requirements file.
48
- The paths could differ for every invocation. Replace them with a predictable path.
49
- """
50
- output_file = Path (output_file )
51
- contents = output_file .read_text ()
52
- contents = contents .replace (absolute_prefix , "" )
53
- contents = re .sub (r"\\(?!(\n|\r\n))" , "/" , contents )
54
- output_file .write_text (contents )
55
-
56
-
57
69
if __name__ == "__main__" :
58
70
if len (sys .argv ) < 4 :
59
71
print (
@@ -75,7 +87,6 @@ def _fix_up_requirements_in_path(absolute_prefix, output_file):
75
87
# absolute prefixes in the locked requirements output file.
76
88
requirements_in_path = Path (requirements_in )
77
89
resolved_requirements_in = str (requirements_in_path .resolve ())
78
- absolute_prefix = resolved_requirements_in [: - len (str (requirements_in_path ))]
79
90
80
91
# Before loading click, set the locale for its parser.
81
92
# If it leaks through to the system setting, it may fail:
@@ -86,7 +97,7 @@ def _fix_up_requirements_in_path(absolute_prefix, output_file):
86
97
os .environ ["LANG" ] = "C.UTF-8"
87
98
88
99
UPDATE = True
89
- # Detect if we are running under `bazel test`
100
+ # Detect if we are running under `bazel test`.
90
101
if "TEST_TMPDIR" in os .environ :
91
102
UPDATE = False
92
103
# pip-compile wants the cache files to be writeable, but if we point
@@ -95,31 +106,13 @@ def _fix_up_requirements_in_path(absolute_prefix, output_file):
95
106
# In theory this makes the test more hermetic as well.
96
107
sys .argv .append ("--cache-dir" )
97
108
sys .argv .append (os .environ ["TEST_TMPDIR" ])
98
- # Make a copy for pip-compile to read and mutate
109
+ # Make a copy for pip-compile to read and mutate.
99
110
requirements_out = os .path .join (
100
111
os .environ ["TEST_TMPDIR" ], os .path .basename (requirements_txt ) + ".out"
101
112
)
102
- copyfile (requirements_txt , requirements_out )
103
-
104
- elif "BUILD_WORKSPACE_DIRECTORY" in os .environ :
105
- # This value, populated when running under `bazel run`, is a path to the
106
- # "root of the workspace where the build was run."
107
- # This matches up with the values passed in via the macro using the 'rootpath' Make variable,
108
- # which for source files provides a path "relative to your workspace root."
109
- #
110
- # Changing to the WORKSPACE root avoids 'file not found' errors when the `.update` target is run
111
- # from different directories within the WORKSPACE.
112
- os .chdir (os .environ ["BUILD_WORKSPACE_DIRECTORY" ])
113
- else :
114
- err_msg = (
115
- "Expected to find BUILD_WORKSPACE_DIRECTORY (running under `bazel run`) or "
116
- "TEST_TMPDIR (running under `bazel test`) in environment."
117
- )
118
- print (
119
- err_msg ,
120
- file = sys .stderr ,
121
- )
122
- sys .exit (1 )
113
+ # Those two files won't necessarily be on the same filesystem, so we can't use os.replace
114
+ # or shutil.copyfile, as they will fail with OSError: [Errno 18] Invalid cross-device link.
115
+ shutil .copy (requirements_txt , requirements_out )
123
116
124
117
update_command = os .getenv ("CUSTOM_COMPILE_COMMAND" ) or "bazel run %s" % (
125
118
update_target_label ,
@@ -137,12 +130,17 @@ def _fix_up_requirements_in_path(absolute_prefix, output_file):
137
130
138
131
if UPDATE :
139
132
print ("Updating " + requirements_txt )
140
- try :
141
- cli ()
142
- except SystemExit as e :
143
- if e .code == 0 :
144
- _fix_up_requirements_in_path (absolute_prefix , requirements_txt )
145
- raise
133
+ if "BUILD_WORKSPACE_DIRECTORY" in os .environ :
134
+ workspace = os .environ ["BUILD_WORKSPACE_DIRECTORY" ]
135
+ requirements_txt_tree = os .path .join (workspace , requirements_txt )
136
+ # In most cases, requirements_txt will be a symlink to the real file in the source tree.
137
+ # If symlinks are not enabled (e.g. on Windows), then requirements_txt will be a copy,
138
+ # and we should copy the updated requirements back to the source tree.
139
+ if not os .path .samefile (requirements_txt , requirements_txt_tree ):
140
+ atexit .register (
141
+ lambda : shutil .copy (requirements_txt , requirements_txt_tree )
142
+ )
143
+ cli ()
146
144
else :
147
145
# cli will exit(0) on success
148
146
try :
@@ -160,7 +158,6 @@ def _fix_up_requirements_in_path(absolute_prefix, output_file):
160
158
)
161
159
sys .exit (1 )
162
160
elif e .code == 0 :
163
- _fix_up_requirements_in_path (absolute_prefix , requirements_out )
164
161
golden_filename = _select_golden_requirements_file (
165
162
requirements_txt ,
166
163
requirements_linux ,
0 commit comments