22import os
33import shutil
44import sys
5+ import tarfile
6+ import tempfile
57import textwrap
68from pathlib import Path
79from tempfile import mkdtemp
2022 CIBW_CACHE_PATH ,
2123 BuildSelector ,
2224 Unbuffered ,
25+ chdir ,
2326 detect_ci_provider ,
27+ format_safe ,
2428)
2529
2630
2731def main () -> None :
28- platform : PlatformName
29-
3032 parser = argparse .ArgumentParser (
31- prog = "cibuildwheel" ,
3233 description = "Build wheels for all the platforms." ,
3334 epilog = """
3435 Most options are supplied via environment variables or in
@@ -66,6 +67,7 @@ def main() -> None:
6667
6768 parser .add_argument (
6869 "--output-dir" ,
70+ type = Path ,
6971 help = "Destination folder for the wheels. Default: wheelhouse." ,
7072 )
7173
@@ -74,19 +76,24 @@ def main() -> None:
7476 default = "" ,
7577 help = """
7678 TOML config file. Default: "", meaning {package}/pyproject.toml,
77- if it exists.
79+ if it exists. To refer to a project inside your project, use {package}
80+ or {project}.
7881 """ ,
7982 )
8083
8184 parser .add_argument (
8285 "package_dir" ,
83- default = "." ,
86+ default = Path ("." ),
87+ type = Path ,
8488 nargs = "?" ,
8589 help = """
86- Path to the package that you want wheels for. Must be a subdirectory of
87- the working directory. When set, the working directory is still
88- considered the 'project' and is copied into the Docker container on
89- Linux. Default: the working directory.
90+ Path to the package that you want wheels for. Must be a
91+ subdirectory of the working directory. When set, the working
92+ directory is still considered the 'project' and is copied into the
93+ Docker container on Linux. Default: the working directory. This can
94+ also be a tar.gz file - if it is, then --config-file and
95+ --output-dir are relative to the current directory, and other paths
96+ are relative to the expanded SDist directory.
9097 """ ,
9198 )
9299
@@ -110,6 +117,50 @@ def main() -> None:
110117
111118 args = parser .parse_args (namespace = CommandLineArguments ())
112119
120+ # These are always relative to the base directory, even in SDist builds
121+ args .package_dir = args .package_dir .resolve ()
122+ args .output_dir = Path (
123+ args .output_dir
124+ if args .output_dir is not None
125+ else os .environ .get ("CIBW_OUTPUT_DIR" , "wheelhouse" )
126+ ).resolve ()
127+
128+ # Standard builds if a directory or non-existent path is given
129+ if not args .package_dir .is_file () and not args .package_dir .name .endswith ("tar.gz" ):
130+ build_in_directory (args )
131+ return
132+
133+ if not args .package_dir .name .endswith ("tar.gz" ):
134+ raise SystemExit ("Must be a tar.gz file if a file is given." )
135+
136+ # Tarfile builds require extraction and changing the directory
137+ with tempfile .TemporaryDirectory (prefix = "cibw-sdist-" ) as temp_dir_str :
138+ temp_dir = Path (temp_dir_str )
139+ with tarfile .open (args .package_dir ) as tar :
140+ tar .extractall (path = temp_dir )
141+
142+ # The extract directory is now the project dir
143+ try :
144+ (project_dir ,) = temp_dir .iterdir ()
145+ except ValueError :
146+ raise SystemExit ("invalid sdist: didn't contain a single dir" ) from None
147+
148+ args .package_dir = project_dir .resolve ()
149+
150+ if args .config_file :
151+ # expand the placeholders if they're used
152+ config_file_path = format_safe (
153+ args .config_file ,
154+ project = project_dir ,
155+ package = project_dir ,
156+ )
157+ args .config_file = str (Path (config_file_path ).resolve ())
158+
159+ with chdir (temp_dir ):
160+ build_in_directory (args )
161+
162+
163+ def build_in_directory (args : CommandLineArguments ) -> None :
113164 if args .platform != "auto" :
114165 platform = args .platform
115166 else :
0 commit comments