@@ -571,12 +571,15 @@ def prepare_new_source(
571571) -> None :
572572 """Default steps for new sources
573573
574+ - ensure .git_archival.txt for setuptools-scm (sdist sources only)
574575 - patch sources
575576 - apply project overrides from settings
576577 - vendor Rust dependencies
577578
578579 :func:`~default_prepare_source` runs this function when the sources are new.
579580 """
581+ if not (source_root_dir / ".git" ).exists ():
582+ ensure_git_archival (version = version , sdist_root_dir = source_root_dir )
580583 patch_source (ctx , source_root_dir , req , version )
581584 pyproject .apply_project_override (
582585 ctx = ctx ,
@@ -681,6 +684,10 @@ def default_build_sdist(
681684 sdist_root_dir = sdist_root_dir ,
682685 build_dir = build_dir ,
683686 )
687+ ensure_git_archival (
688+ version = version ,
689+ sdist_root_dir = build_dir ,
690+ )
684691 # The format argument is specified based on
685692 # https://peps.python.org/pep-0517/#build-sdist.
686693 with tarfile .open (sdist_filename , "x:gz" , format = tarfile .PAX_FORMAT ) as sdist :
@@ -762,6 +769,66 @@ def ensure_pkg_info(
762769 return had_pkg_info
763770
764771
772+ # Template .git_archival.txt files contain "$Format:…$" placeholders that
773+ # `git archive` expands into real values. If they survive unexpanded,
774+ # setuptools-scm detects "$FORMAT" in the node field and returns no version
775+ # (see setuptools_scm.git.archival_to_version).
776+ _UNPROCESSED_ARCHIVAL_MARKER = "$Format:"
777+
778+ # Dummy commit hash used when synthesizing .git_archival.txt without a
779+ # real git repository. The value is never interpreted by setuptools-scm
780+ # beyond checking that it is not an unprocessed $Format:…$ placeholder.
781+ _DUMMY_NODE = "0" * 40
782+
783+ _GIT_ARCHIVAL_CONTENT = """\
784+ node: {node}
785+ node-date: 1970-01-01T00:00:00+00:00
786+ describe-name: {version}-0-g{node}
787+ """
788+
789+
790+ def ensure_git_archival (
791+ * ,
792+ version : Version ,
793+ sdist_root_dir : pathlib .Path ,
794+ ) -> bool :
795+ """Ensure that sdist has a usable ``.git_archival.txt`` for setuptools-scm.
796+
797+ When building from source archives without a ``.git`` directory,
798+ setuptools-scm cannot determine the package version. A synthesized
799+ ``.git_archival.txt`` provides the version through the ``describe-name``
800+ field so that setuptools-scm resolves it without requiring an environment
801+ variable override.
802+
803+ See https://setuptools-scm.readthedocs.io/en/latest/usage/#git-archives
804+
805+ Returns True if a valid archival file was already present (no changes
806+ made), False if a new file was written (file was missing or contained
807+ unprocessed placeholders).
808+ """
809+ archival_file = sdist_root_dir / ".git_archival.txt"
810+
811+ if archival_file .is_file ():
812+ content = archival_file .read_text ()
813+ if _UNPROCESSED_ARCHIVAL_MARKER not in content :
814+ logger .debug (
815+ "valid .git_archival.txt already present in %s" , sdist_root_dir
816+ )
817+ return True
818+ logger .warning ("replacing unprocessed .git_archival.txt in %s" , sdist_root_dir )
819+
820+ archival_file .write_text (
821+ _GIT_ARCHIVAL_CONTENT .format (
822+ node = _DUMMY_NODE ,
823+ version = str (version ),
824+ )
825+ )
826+ logger .info (
827+ "created .git_archival.txt for version %s in %s" , version , sdist_root_dir
828+ )
829+ return False
830+
831+
765832def validate_sdist_filename (
766833 req : Requirement ,
767834 version : Version ,
0 commit comments