Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
4cd2811
Proof of concept
perseoGI May 16, 2025
156e036
Simplify
perseoGI May 16, 2025
c7b5dff
Added tar_compressor
perseoGI May 16, 2025
be30ba2
Extra simplify
perseoGI May 16, 2025
1295d4f
Cache the plugin load
perseoGI May 19, 2025
cc4d3ad
WIP
perseoGI May 19, 2025
e245a93
Merge with develop2
perseoGI May 20, 2025
f094d9a
Moved plugin load to ConfigAPI
perseoGI May 20, 2025
fee5fab
Pass config_api to remote_manager and local_recipe_index
perseoGI May 20, 2025
452f774
Restore previous tar_extract fixing tests
perseoGI May 20, 2025
c1a7320
Removed compression.py module and minimize diff
perseoGI May 22, 2025
a240c8c
Remove debug print
perseoGI May 22, 2025
cf7de74
Applied thread suggestions
perseoGI May 23, 2025
557f8e0
Remove created pkglist.json on cache restore after usage
perseoGI May 23, 2025
f82d764
Remove unused and avoid rechecking FS
perseoGI May 26, 2025
549e086
Merge branch 'develop2' into pgi/plugin/compression
perseoGI May 26, 2025
ac1fc45
Added test to check extract failure and issue #18259 test
perseoGI May 26, 2025
181a736
Added config to compression.py interface and renamed parameters
perseoGI May 27, 2025
3b32ec2
Fix invokation
perseoGI May 27, 2025
b52bce2
Rename config for conf
perseoGI May 27, 2025
91da7a4
Added ref on test
perseoGI May 27, 2025
e96bd3c
Merge branch 'develop2' into pgi/plugin/compression
perseoGI May 27, 2025
a024baf
Merge branch 'develop2' into pgi/plugin/compression
perseoGI Jun 19, 2025
989fde9
Move to different approach: tar encapsulation respecting extensions
perseoGI Jun 20, 2025
f986651
Fix condition error
perseoGI Jun 23, 2025
8009bed
Addressed some issues
perseoGI Jun 25, 2025
0db1a7b
Make plugin return compressed extension
perseoGI Jun 25, 2025
dcbb29e
Adapt changes to support metadata in wrapped files
perseoGI Jul 14, 2025
28e9148
Merged with develop2 and moved compression_plugin to CacheAPI
perseoGI Aug 20, 2025
8b50ae0
branching from Perseo
memsharded Jan 13, 2026
67a40b2
review
memsharded Jan 13, 2026
04f9207
wip
memsharded Jan 14, 2026
4cea425
wip
memsharded Jan 14, 2026
1f8d7a6
wip
memsharded Jan 28, 2026
0394cab
Merge branch 'develop2' into feature/compression_plugin
memsharded Feb 25, 2026
a6106b3
fix CMakeConfigDeps casing in-package (#19669)
memsharded Feb 25, 2026
2a77a33
Use lazy imports to avoid circular dependency so PyInstaller bundles …
czoido Feb 25, 2026
90bd6c0
Expose PyEnv root and python executable (#19628)
davidsanfal Feb 25, 2026
0a713f9
Avoid missing binaries due to default platform requires revision (#19…
AbrilRBS Feb 27, 2026
5ac1cc4
Correct encoding of make_program path for AutoTools + msys2 (#15047)
nmasseyKM Mar 2, 2026
af38e13
Add support for Apple IS 26.3 (#19691)
AbrilRBS Mar 2, 2026
efba35c
Create stubs correctly for user CMake presets when user_presets_path …
czoido Mar 3, 2026
20d0fa5
fix win_bash_true with scope=run (#19703)
memsharded Mar 4, 2026
fa8a6e6
implicit C linkage support by CXX projects in CMakeConfigDeps (#19704)
memsharded Mar 4, 2026
fba0351
Representation of a cycle/loop in the graph info with a red arrow (#1…
ErniGH Mar 4, 2026
0485a86
Add support for Clang version 22 (#19709)
mathbunnyru Mar 4, 2026
2bdab34
Fix CMake escape error (#19706)
memsharded Mar 5, 2026
f68019c
Fix exception with corrupted recipes (#19713)
AbrilRBS Mar 5, 2026
8338a01
show transitive html (#19725)
memsharded Mar 9, 2026
5d2f00d
add trace to DB timeout (#19728)
memsharded Mar 9, 2026
ba2d6e7
Tests/improves (#19727)
memsharded Mar 9, 2026
c5e278f
Tests/slow (#19720)
memsharded Mar 10, 2026
aa4addc
Fix/source creds msg (#19737)
memsharded Mar 11, 2026
488308a
Fix detect_emcc_compiler (#19735)
stevenwdv Mar 11, 2026
37b9e82
remove alias support (#19740)
memsharded Mar 11, 2026
0115fa1
PyEnv output based on verbosity level (#19731)
davidsanfal Mar 13, 2026
995eadc
Feature/build arg context (pure refactor) (#19746)
memsharded Mar 13, 2026
e5b01b2
fix platform_requires definition (#19750)
memsharded Mar 13, 2026
b498010
Only group build packages in `conan graph info .. -f=html` (#19744)
AbrilRBS Mar 13, 2026
fe60b20
fix CMakeConfigDeps .set_property context (#19760)
memsharded Mar 15, 2026
41a4d71
Fix/overrides lockfiles (#19739)
memsharded Mar 16, 2026
f253d3c
fix issue with --build=editable with download source (#19758)
memsharded Mar 16, 2026
b254f3c
moving collaborators from ConanApp to ApiHelpers (#19755)
memsharded Mar 19, 2026
462a523
CMakeToolchain: disable CMake package registry exports (#19766)
ReinerBRO Mar 20, 2026
217fc00
adding rcflags (#19693)
memsharded Mar 20, 2026
a5cd2f5
CMake.configure() extra args, via CMakePresets (#19639)
memsharded Mar 23, 2026
15148c9
Minor improvements (#19778)
memsharded Mar 23, 2026
768531b
Fix NMake integrations to handle hexadecimal values in defines (#19779)
memsharded Mar 23, 2026
4e0bf67
fix DB connection timeout msg (#19781)
memsharded Mar 23, 2026
a5d453a
Fix missing libraries in legacy `<packagename>_LIBRARIES` variable de…
AbrilRBS Mar 24, 2026
d820ef1
[GH] Update clang major version (#19785)
franramirez688 Mar 24, 2026
49810d3
allowing tool-requires negated or (#19780)
memsharded Mar 25, 2026
3004d93
Add cve version info in `conan audit` results (#19774)
AbrilRBS Mar 25, 2026
3500931
Conan 2.27.0
czoido Mar 25, 2026
a4a7f77
Conan 2.28.0 dev
czoido Mar 25, 2026
80210c0
Feature/cachyos package manager (#19788)
LinusVanElswijk Mar 26, 2026
13a89a2
Add missing `CMAKE_CXX_COMPILER_WORKS` and family (#19708)
AbrilRBS Mar 26, 2026
eef9e93
short form for --update-requires (#19791)
memsharded Mar 26, 2026
89711b4
Add support for Apple Clang version 21 (#19795)
Dahlgren Mar 27, 2026
29a20a7
Test improvements (#19800)
memsharded Mar 27, 2026
f7853eb
Fix syntax error in Python <3.9 (#19805)
AbrilRBS Mar 27, 2026
cb806e4
avoid casing errors while looking for version ranges (#19799)
memsharded Mar 30, 2026
27cb3fe
Undefined locale for list html (#19828)
AbrilRBS Apr 6, 2026
dc2cefb
finalize() output folder should be printed only once (#19834)
perseoGI Apr 6, 2026
ecb8c39
Refactor/ordered dict (#19839)
memsharded Apr 7, 2026
336b580
fix powershell unset (and other possible unsets too) (#19820)
memsharded Apr 7, 2026
57f643d
doc retry conf and change default (#19830)
memsharded Apr 7, 2026
c6fd691
Documentation improvement for items() iteration of packages list (#19…
AbrilRBS Apr 7, 2026
54720f3
Poc/editable refactor (#19835)
memsharded Apr 7, 2026
683e075
testing msvc minor update via conf (#19843)
memsharded Apr 8, 2026
035961f
Avoid detecting default package manager when overrided from profile (…
perseoGI Apr 8, 2026
6f4cb53
Add `--strict` flag to `conan remote auth` (#19848)
czoido Apr 8, 2026
fc22da0
Propagate build requirement run trait to upstream shared dependency (…
AbrilRBS Apr 8, 2026
2455923
Refactor/api helpers (#19797)
memsharded Apr 10, 2026
102fa41
Internal improvements (#19852)
memsharded Apr 10, 2026
4f5febb
Forward underlying system package manager error messages (#19858)
perseoGI Apr 13, 2026
e3c6499
Revert quote changes for nmake defines (#19859)
AbrilRBS Apr 13, 2026
912a4f2
remove pytest version limit (#19864)
memsharded Apr 14, 2026
e54f73e
refactor for loader api-helper (#19866)
memsharded Apr 14, 2026
44bac65
fix error option output (#19867)
danimtb Apr 15, 2026
c3bc19c
Test for absolute core.sources:download_cache (#19869)
AbrilRBS Apr 15, 2026
c2ec220
api docstrings (#19871)
memsharded Apr 15, 2026
de9f9c9
Warn when credentials env vars are ignored due to anonymous server ac…
czoido Apr 15, 2026
19fe112
tools.build:install_strip now accepts a list of possible build system…
perseoGI Apr 16, 2026
028c0c7
Modernize build-order tests to use order-by (#19878)
AbrilRBS Apr 16, 2026
1ee0b08
Allow patterns in `--update` flag (#19856)
AbrilRBS Apr 16, 2026
ffcb040
Remove some deprecated features (#19877)
AbrilRBS Apr 17, 2026
49f72a4
Refactor/sources caching downloaded (#19789)
memsharded Apr 17, 2026
703eae0
checking config_requires from lockfiles at conan-install time (#19875)
memsharded Apr 21, 2026
490772b
detect_libcxx: Remove temporary directory usage, using safe parameter…
aagor Apr 21, 2026
a5cdf4c
poc of changing transitive statics package_id (#19705)
memsharded Apr 21, 2026
e726aad
Scoped output: first approach (#19836)
perseoGI Apr 21, 2026
cf01c96
Colorize config command outputs (#19889)
AbrilRBS Apr 21, 2026
c3764a8
HTML updates for `conan report diff` and `conan graph info` (#19884)
AbrilRBS Apr 21, 2026
509fa37
Validate conf on init (#19886)
AbrilRBS Apr 21, 2026
7de7408
refactor _compute_fix_transitive() (#19890)
memsharded Apr 21, 2026
7e9be12
Simplify scoped output logic (#19897)
perseoGI Apr 22, 2026
0c7e8dc
wip
memsharded Apr 22, 2026
02e134f
wip
memsharded Apr 22, 2026
172cb8a
updated from dvelop2
memsharded Jun 24, 2026
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
23 changes: 22 additions & 1 deletion conan/api/conan_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,25 @@ def __init__(self, conan_api):
self.cache = PkgCache(self._conan_api.home_folder, self.global_conf)
self._settings_yml = None
self._remote_manager = None
self._compression_plugin = None

@property
def compression_plugin(self):
# NOTE: Users cannot store a reference to this plugin, otherwise it will not
# be updated after a "conan config install/install-pkg"
if self._compression_plugin is None:
compression_plugin_path = HomePaths(
self._conan_api.home_folder).compression_plugin_path
if not os.path.exists(compression_plugin_path):
self._compression_plugin = False # Avoid FS re-check
return None
mod, _ = load_python_file(compression_plugin_path)
# A plugin can provide just 1 of them
if not hasattr(mod, "tar_extract") and not hasattr(mod, "tar_compress"):
raise ConanException("The 'compression.py' plugin does not contain "
"required `tar_extract` or `tar_compress` functions")
self._compression_plugin = mod
return self._compression_plugin

def set_core_confs(self, core_confs):
confs = ConfDefinition()
Expand Down Expand Up @@ -165,6 +184,7 @@ def reinit(self):
self.cache = PkgCache(self._conan_api.home_folder, self.global_conf)
self._remote_manager = None
self._editable_packages = EditablePackages(self._conan_api.home_folder)
self._compression_plugin = None

@property
def settings_yml(self):
Expand All @@ -180,7 +200,8 @@ def remote_manager(self):
requester = self._conan_api._api_helpers.requester # noqa
auth_manager = ConanApiAuthManager(requester, self._conan_api.home_folder, localdb,
self.global_conf)
self._remote_manager = RemoteManager(self.cache, auth_manager, home_folder)
self._remote_manager = RemoteManager(self.cache, auth_manager, home_folder,
self.compression_plugin)
return self._remote_manager

@property
Expand Down
36 changes: 27 additions & 9 deletions conan/api/subapi/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,10 @@ def sign(self, package_list):
"https://docs.conan.io/2/reference/extensions/package_signing.html.")

loader = self._api_helpers.loader
compress_plugin = self._api_helpers.compression_plugin
preparator = PackagePreparator(loader, self._api_helpers.cache,
self._api_helpers.remote_manager,
self._api_helpers.global_conf)
self._api_helpers.global_conf, compress_plugin)
# Some packages can have missing sources/exports_sources
enabled_remotes = self._conan_api.remotes.list()
preparator.prepare(package_list, enabled_remotes, None, force=True)
Expand Down Expand Up @@ -373,7 +374,11 @@ def save(self, package_list: PackagesList, path, no_source=False) -> None:
pkglist_path = os.path.join(tempfile.gettempdir(), "pkglist.json")
save(pkglist_path, serialized)
tar_files["pkglist.json"] = pkglist_path
compress_files(tar_files, tgz_name, os.path.dirname(path), compresslevel, recursive=True)

plugin = self._conan_api._api_helpers.compression_plugin # noqa
compress_plugin = getattr(plugin, "tar_compress", None) if plugin else None
compress_files(tar_files, tgz_name, os.path.dirname(path), compresslevel,
recursive=True, compress_plugin=compress_plugin)
remove(pkglist_path)
ConanOutput().success(f"Created cache save file: {path}")

Expand All @@ -391,13 +396,26 @@ def restore(self, path) -> PackagesList:
cache = PkgCache(self._conan_api.cache_folder, self._api_helpers.global_conf)
cache_folder = cache.store # Note, this is not the home, but the actual package cache

with open(path, mode='rb') as file_handler:
the_tar = tarfile.open(fileobj=file_handler)
fileobj = the_tar.extractfile("pkglist.json")
pkglist = fileobj.read()
the_tar.extraction_filter = (lambda member, _: member) # fully_trusted (Py 3.14)
the_tar.extractall(path=cache_folder)
the_tar.close()
plugin = self._conan_api._api_helpers.compression_plugin # noqa
extract_plugin = getattr(plugin, "tar_extract", None) if plugin else None
extracted = False
if extract_plugin:
extracted = extract_plugin(path, cache_folder, scope="Restore")
# If the plugin returns false, fallback to Conan extraction

if extracted is False:
with open(path, mode='rb') as file_handler:
the_tar = tarfile.open(fileobj=file_handler)
the_tar.extraction_filter = (lambda member, _: member) # fully_trusted (Py 3.14)
the_tar.extractall(path=cache_folder)
the_tar.close()

# Retrieve the package list from the already extracted archive
pkglist_path = os.path.join(cache_folder, "pkglist.json")
with open(pkglist_path) as file_handler:
pkglist = file_handler.read()
# Delete the pkglist.json file to keep cache clean
remove(pkglist_path)

# After unzipping the files, we need to update the DB that references these files
out = ConanOutput()
Expand Down
3 changes: 2 additions & 1 deletion conan/api/subapi/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ def prepare(self, package_list: PackagesList, enabled_remotes: List[Remote],
raise ConanException("Empty string and patterns can not be mixed for metadata.")

loader = self._api_helpers.loader
compress_plugin = self._api_helpers.compression_plugin
preparator = PackagePreparator(loader, self._api_helpers.cache,
self._api_helpers.remote_manager,
self._api_helpers.global_conf)
self._api_helpers.global_conf, compress_plugin)
preparator.prepare(package_list, enabled_remotes, metadata)
signer = PkgSignaturesPlugin(self._api_helpers.cache, self._conan_api.home_folder)
if signer.is_sign_configured:
Expand Down
18 changes: 15 additions & 3 deletions conan/internal/api/uploader.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def get_compress_level(compressformat, global_conf):


class PackagePreparator:
def __init__(self, loader, cache, remote_manager, global_conf):
def __init__(self, loader, cache, remote_manager, global_conf, compress_plugin):
self._loader = loader
self._remote_manager = remote_manager
self._cache = cache
Expand All @@ -113,6 +113,8 @@ def __init__(self, loader, cache, remote_manager, global_conf):
compresslevel = get_compress_level(compressformat, global_conf)
self._compressformat = compressformat
self._compresslevel = compresslevel
plugin = compress_plugin # noqa
self._compress_plugin = getattr(plugin, "tar_compress", None) if plugin else None

def prepare(self, pkg_list, enabled_remotes, metadata, force=False):
local_url = self._global_conf.get("core.scm:local_url", choices=["allow", "block"])
Expand Down Expand Up @@ -243,7 +245,8 @@ def _compressed_file(self, filename, files, download_folder, ref):
file_name = filename + self._compressformat
package_file = os.path.join(download_folder, file_name)
compressed_path = compress_files(files, file_name, download_folder,
compresslevel=self._compresslevel, scope=str(ref))
compresslevel=self._compresslevel, scope=str(ref),
compress_plugin=self._compress_plugin)
assert compressed_path == package_file
assert os.path.exists(package_file)
return file_name
Expand Down Expand Up @@ -336,13 +339,22 @@ def gzopen_without_timestamps(name, fileobj, compresslevel=None):
return t


def compress_files(files, name, dest_dir, compresslevel=None, scope=None, recursive=False):
def compress_files(files, name, dest_dir, compresslevel=None, scope=None, recursive=False,
compress_plugin=None):
t1 = time.time()
tgz_path = os.path.join(dest_dir, name)

out = ConanOutput(scope=scope)
out.info(f"Compressing {name}")

if compress_plugin is not None:
out.info(f"Compressing {name} using compression plugin")
compressed = compress_plugin(archive_path=tgz_path, files=files, recursive=recursive,
compresslevel=compresslevel, scope=scope)
if compressed is not False:
out.debug(f"{name} compressed in {time.time() - t1} time")
return tgz_path

if name.endswith("zst"):
with tarfile.open(tgz_path, "w:zst", level=compresslevel) as tar: # noqa Py314 only
for filename, abs_path in sorted(files.items()):
Expand Down
4 changes: 4 additions & 0 deletions conan/internal/cache/home_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,7 @@ def settings_path_user(self):
@property
def config_version_path(self):
return os.path.join(self._home, "config_version.json")

@property
def compression_plugin_path(self):
return os.path.join(self._home, _EXTENSIONS_FOLDER, _PLUGINS, "compression.py")
26 changes: 19 additions & 7 deletions conan/internal/rest/remote_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ class RemoteManager:

_ErrorMsg = namedtuple("ErrorMsg", ["message"])

def __init__(self, cache, auth_manager, home_folder):
def __init__(self, cache, auth_manager, home_folder, compression_plugin=None):
self._cache = cache
self._auth_manager = auth_manager
self._signer = PkgSignaturesPlugin(cache, home_folder)
self._home_folder = home_folder
self._extract_plugin = getattr(compression_plugin, "tar_extract", None) \
if compression_plugin else None

def _local_folder_remote(self, remote):
if remote.remote_type == LOCAL_RECIPES_INDEX:
Expand Down Expand Up @@ -99,7 +101,8 @@ def _download_recipe(self, layout, ref, remote, metadata):
tgz_file = zipped_files.pop(export_file, None)

if tgz_file:
uncompress_file(tgz_file, export_folder, scope=str(ref))
uncompress_file(tgz_file, export_folder, scope=str(ref),
extract_plugin=self._extract_plugin)
mkdir(export_folder)
for file_name, file_path in zipped_files.items(): # copy CONANFILE
shutil.move(file_path, os.path.join(export_folder, file_name))
Expand Down Expand Up @@ -136,7 +139,8 @@ def get_recipe_sources(self, ref, layout, remote):
self._signer.verify(ref, download_folder, layout.metadata(), files=zipped_files)
# Only 1 file is guaranteed
tgz_file = next(iter(zipped_files.values()))
uncompress_file(tgz_file, export_sources_folder, scope=str(ref))
uncompress_file(tgz_file, export_sources_folder, scope=str(ref),
extract_plugin=self._extract_plugin)

def get_package(self, pref, remote, metadata=None):
output = ConanOutput(scope=str(pref.ref))
Expand Down Expand Up @@ -191,7 +195,8 @@ def _get_package(self, layout, pref, remote, scoped_output, metadata):

tgz_file = zipped_files.pop(package_file)
package_folder = layout.package()
uncompress_file(tgz_file, package_folder, scope=str(pref.ref))
uncompress_file(tgz_file, package_folder, scope=str(pref.ref),
extract_plugin=self._extract_plugin)
mkdir(package_folder) # Just in case it doesn't exist, because uncompress did nothing
for file_name, file_path in zipped_files.items(): # copy CONANINFO and CONANMANIFEST
shutil.move(file_path, os.path.join(package_folder, file_name))
Expand Down Expand Up @@ -342,18 +347,25 @@ def _call_remote(self, remote, method, *args, **kwargs):
raise ConanException(exc, remote=remote)


def uncompress_file(src_path, dest_folder, scope=None):
if sys.version_info.minor < 14 and src_path.endswith("zst"):
def uncompress_file(src_path, dest_folder, scope=None, extract_plugin=None):
if sys.version_info.minor < 14 and src_path.endswith("zst") and extract_plugin is None:
raise ConanException(f"File {os.path.basename(src_path)} compressed with 'zst', "
f"unsupported for Python<3.14 ")

try:
filesize = os.path.getsize(src_path)
big_file = filesize > 10000000 # 10 MB
if big_file:
hs = human_size(filesize)
ConanOutput(scope=scope).info(f"Decompressing {hs} {os.path.basename(src_path)}")

if extract_plugin is not None:
if extract_plugin(src_path, dest_folder, scope) is not False:
# If the plugin returns false, fallback to Conan extraction
return

with open(src_path, mode='rb') as file_handler:
tar_extract(file_handler, dest_folder)
tar_extract(fileobj=file_handler, destination_dir=dest_folder)
except Exception as e:
error_msg = "Error while extracting downloaded file '%s' to %s\n%s\n"\
% (src_path, dest_folder, str(e))
Expand Down
4 changes: 4 additions & 0 deletions test/integration/command/cache/test_cache_save_restore.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,13 @@ def test_cache_save_excluded_folders():

# exclude source
c.run("cache save * --no-source")
# Check default compression function is being used and not compression.py plugin one
assert "Compressing conan_cache_save.tgz\n" in c.out
c3 = TestClient()
shutil.copy2(cache_path, c3.current_folder)
c3.run("cache restore conan_cache_save.tgz")
# Default decompress does not have any output
assert "Decompressing conan_cache_save.tgz" not in c3.out
ref_layout = c3.get_latest_ref_layout(ref)
assert not os.path.exists(os.path.join(ref_layout.source(), "mysrc.c"))

Expand Down
Loading
Loading