Skip to content

Commit

Permalink
merged develop2
Browse files Browse the repository at this point in the history
  • Loading branch information
memsharded committed Jan 21, 2025
2 parents 0dfebbc + eb95f5b commit a7d6d88
Show file tree
Hide file tree
Showing 229 changed files with 1,083 additions and 1,217 deletions.
6 changes: 3 additions & 3 deletions conan/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from conans.model.conan_file import ConanFile
from conan.tools.scm import Version as _Version
from conan.internal.model.conan_file import ConanFile
from conan.internal.model.version import Version

__version__ = '2.12.0-dev'
conan_version = _Version(__version__)
conan_version = Version(__version__)
2 changes: 1 addition & 1 deletion conan/api/conan_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from conans.client.migrations import ClientMigrator
from conan.errors import ConanException
from conan.internal.paths import get_conan_user_home
from conans.model.version_range import validate_conan_version
from conan.internal.model.version_range import validate_conan_version


class ConanAPI:
Expand Down
3 changes: 3 additions & 0 deletions conan/api/model/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from conan.api.model.remote import Remote, LOCAL_RECIPES_INDEX
from conan.api.model.refs import RecipeReference, PkgReference
from conan.api.model.list import PackagesList, MultiPackagesList, ListPattern
46 changes: 4 additions & 42 deletions conan/api/model.py → conan/api/model/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,13 @@
import os
from json import JSONDecodeError

from conan.errors import ConanException
from conan.internal.errors import NotFoundException
from conan.api.model import RecipeReference, PkgReference
from conan.internal.model.version_range import VersionRange
from conans.client.graph.graph import RECIPE_EDITABLE, RECIPE_CONSUMER, RECIPE_PLATFORM, \
RECIPE_VIRTUAL, BINARY_SKIP, BINARY_MISSING, BINARY_INVALID
from conan.internal.errors import NotFoundException
from conan.errors import ConanException
from conans.model.package_ref import PkgReference
from conans.model.recipe_ref import RecipeReference
from conans.util.files import load
from conans.model.version_range import VersionRange

LOCAL_RECIPES_INDEX = "local-recipes-index"


class Remote:

def __init__(self, name, url, verify_ssl=True, disabled=False, allowed_packages=None,
remote_type=None):
self.name = name # Read only, is the key
self.url = url
self.verify_ssl = verify_ssl
self.disabled = disabled
self.allowed_packages = allowed_packages
self.remote_type = remote_type
self.caching = {}

def __eq__(self, other):
if other is None:
return False
return (self.name == other.name and self.url == other.url and
self.verify_ssl == other.verify_ssl and self.disabled == other.disabled)

def __str__(self):
allowed_msg = ""
if self.allowed_packages:
allowed_msg = ", Allowed packages: {}".format(", ".join(self.allowed_packages))
if self.remote_type == LOCAL_RECIPES_INDEX:
return "{}: {} [{}, Enabled: {}{}]".format(self.name, self.url, LOCAL_RECIPES_INDEX,
not self.disabled, allowed_msg)
return "{}: {} [Verify SSL: {}, Enabled: {}{}]".format(self.name, self.url, self.verify_ssl,
not self.disabled, allowed_msg)

def __repr__(self):
return str(self)

def invalidate_cache(self):
self.caching = {}


class MultiPackagesList:
Expand Down
306 changes: 306 additions & 0 deletions conan/api/model/refs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@

import fnmatch
import re
from functools import total_ordering

from conan.errors import ConanException
from conan.internal.model.version import Version
from conans.util.dates import timestamp_to_str


@total_ordering
class RecipeReference:
""" an exact (no version-range, no alias) reference of a recipe.
Should be enough to locate a recipe in the cache or in a server
Validation will be external to this class, at specific points (export, api, etc)
"""

def __init__(self, name=None, version=None, user=None, channel=None, revision=None,
timestamp=None):
self.name = name
if version is not None and not isinstance(version, Version):
version = Version(version)
self.version = version # This MUST be a version if we want to be able to order
self.user = user
self.channel = channel
self.revision = revision
self.timestamp = timestamp

def copy(self):
# Used for creating copy in lockfile-overrides mechanism
return RecipeReference(self.name, self.version, self.user, self.channel, self.revision,
self.timestamp)

def __repr__(self):
""" long repr like pkg/0.1@user/channel#rrev%timestamp """
result = self.repr_notime()
if self.timestamp is not None:
result += "%{}".format(self.timestamp)
return result

def repr_notime(self):
result = self.__str__()
if self.revision is not None:
result += "#{}".format(self.revision)
return result

def repr_reduced(self):
result = self.__str__()
if self.revision is not None:
result += "#{}".format(self.revision[0:4])
return result

def repr_humantime(self):
result = self.repr_notime()
assert self.timestamp
result += " ({})".format(timestamp_to_str(self.timestamp))
return result

def __str__(self):
""" shorter representation, excluding the revision and timestamp """
if self.name is None:
return ""
result = "/".join([self.name, str(self.version)])
if self.user:
result += "@{}".format(self.user)
if self.channel:
assert self.user
result += "/{}".format(self.channel)
return result

def __lt__(self, ref):
# The timestamp goes before the revision for ordering revisions chronologically
# In theory this is enough for sorting
# When no timestamp is given, it will always have lower priority, to avoid comparison
# errors float <> None
return (self.name, self.version, self.user or "", self.channel or "", self.timestamp or 0,
self.revision or "") \
< (ref.name, ref.version, ref.user or "", ref.channel or "", ref.timestamp or 0,
ref.revision or "")

def __eq__(self, ref):
# Timestamp doesn't affect equality.
# This is necessary for building an ordered list of UNIQUE recipe_references for Lockfile
if ref is None:
return False
# If one revision is not defined, they are equal
if self.revision is not None and ref.revision is not None:
return (self.name, self.version, self.user, self.channel, self.revision) == \
(ref.name, ref.version, ref.user, ref.channel, ref.revision)
return (self.name, self.version, self.user, self.channel) == \
(ref.name, ref.version, ref.user, ref.channel)

def __hash__(self):
# This is necessary for building an ordered list of UNIQUE recipe_references for Lockfile
return hash((self.name, self.version, self.user, self.channel))

@staticmethod
def loads(rref):
try:
# timestamp
tokens = rref.rsplit("%", 1)
text = tokens[0]
timestamp = float(tokens[1]) if len(tokens) == 2 else None

# revision
tokens = text.split("#", 1)
ref = tokens[0]
revision = tokens[1] if len(tokens) == 2 else None

# name, version always here
tokens = ref.split("@", 1)
name, version = tokens[0].split("/", 1)
assert name and version
# user and channel
if len(tokens) == 2 and tokens[1]:
tokens = tokens[1].split("/", 1)
user = tokens[0] if tokens[0] else None
channel = tokens[1] if len(tokens) == 2 else None
else:
user = channel = None
return RecipeReference(name, version, user, channel, revision, timestamp)
except Exception:
from conan.errors import ConanException
raise ConanException(
f"{rref} is not a valid recipe reference, provide a reference"
f" in the form name/version[@user/channel]")

def validate_ref(self, allow_uppercase=False):
""" at the moment only applied to exported (exact) references, but not for requires
that could contain version ranges
"""
from conan.api.output import ConanOutput
self_str = str(self)
if self_str != self_str.lower():
if not allow_uppercase:
raise ConanException(f"Conan packages names '{self_str}' must be all lowercase")
else:
ConanOutput().warning(f"Package name '{self_str}' has uppercase, and has been "
"allowed by temporary config. This will break in later 2.X")
if len(self_str) > 200:
raise ConanException(f"Package reference too long >200 {self_str}")
if ":" in repr(self):
raise ConanException(f"Invalid recipe reference '{repr(self)}' is a package reference")
if not allow_uppercase:
validation_pattern = re.compile(r"^[a-z0-9_][a-z0-9_+.-]{1,100}\Z")
else:
validation_pattern = re.compile(r"^[a-zA-Z0-9_][a-zA-Z0-9_+.-]{1,100}\Z")
if validation_pattern.match(self.name) is None:
raise ConanException(f"Invalid package name '{self.name}'")
if validation_pattern.match(str(self.version)) is None:
raise ConanException(f"Invalid package version '{self.version}'")
if self.user and validation_pattern.match(self.user) is None:
raise ConanException(f"Invalid package user '{self.user}'")
if self.channel and validation_pattern.match(self.channel) is None:
raise ConanException(f"Invalid package channel '{self.channel}'")

# Warn if they use .+ in the name/user/channel, as it can be problematic for generators
pattern = re.compile(r'[.+]')
if pattern.search(self.name):
ConanOutput().warning(f"Name containing special chars is discouraged '{self.name}'")
if self.user and pattern.search(self.user):
ConanOutput().warning(f"User containing special chars is discouraged '{self.user}'")
if self.channel and pattern.search(self.channel):
ConanOutput().warning(f"Channel containing special chars is discouraged "
f"'{self.channel}'")

def matches(self, pattern, is_consumer):
negate = False
if pattern.startswith("!") or pattern.startswith("~"):
pattern = pattern[1:]
negate = True

no_user_channel = False
if pattern.endswith("@"): # it means we want to match only without user/channel
pattern = pattern[:-1]
no_user_channel = True
elif "@#" in pattern:
pattern = pattern.replace("@#", "#")
no_user_channel = True

condition = ((pattern == "&" and is_consumer) or
fnmatch.fnmatchcase(str(self), pattern) or
fnmatch.fnmatchcase(self.repr_notime(), pattern))
if no_user_channel:
condition = condition and not self.user and not self.channel
if negate:
return not condition
return condition

def partial_match(self, pattern):
"""
Finds if pattern matches any of partial sums of tokens of conan reference
"""
tokens = [self.name, "/", str(self.version)]
if self.user:
tokens += ["@", self.user]
if self.channel:
tokens += ["/", self.channel]
if self.revision:
tokens += ["#", self.revision]
partial = ""
for token in tokens:
partial += token
if pattern.match(partial):
return True


class PkgReference:

def __init__(self, ref=None, package_id=None, revision=None, timestamp=None):
self.ref = ref
self.package_id = package_id
self.revision = revision
self.timestamp = timestamp # float, Unix seconds UTC

def __repr__(self):
""" long repr like pkg/0.1@user/channel#rrev%timestamp """
if self.ref is None:
return ""
result = repr(self.ref)
if self.package_id:
result += ":{}".format(self.package_id)
if self.revision is not None:
result += "#{}".format(self.revision)
if self.timestamp is not None:
result += "%{}".format(self.timestamp)
return result

def repr_notime(self):
if self.ref is None:
return ""
result = self.ref.repr_notime()
if self.package_id:
result += ":{}".format(self.package_id)
if self.revision is not None:
result += "#{}".format(self.revision)
return result

def repr_reduced(self):
if self.ref is None:
return ""
result = self.ref.repr_reduced()
if self.package_id:
result += ":{}".format(self.package_id[0:4])
if self.revision is not None:
result += "#{}".format(self.revision[0:4])
return result

def repr_humantime(self):
result = self.repr_notime()
assert self.timestamp
result += " ({})".format(timestamp_to_str(self.timestamp))
return result

def __str__(self):
""" shorter representation, excluding the revision and timestamp """
if self.ref is None:
return ""
result = str(self.ref)
if self.package_id:
result += ":{}".format(self.package_id)
return result

def __lt__(self, ref):
# The timestamp goes before the revision for ordering revisions chronologically
raise Exception("WHO IS COMPARING PACKAGE REFERENCES?")
# return (self.name, self.version, self.user, self.channel, self.timestamp, self.revision) \
# < (ref.name, ref.version, ref.user, ref.channel, ref._timestamp, ref.revision)

def __eq__(self, other):
# TODO: In case of equality, should it use the revision and timestamp?
# Used:
# at "graph_binaries" to check: cache_latest_prev != pref
# at "installer" to check: if pkg_layout.reference != pref (probably just optimization?)
# at "revisions_test"
return self.ref == other.ref and self.package_id == other.package_id and \
self.revision == other.revision

def __hash__(self):
# Used in dicts of PkgReferences as keys like the cached nodes in the graph binaries
return hash((self.ref, self.package_id, self.revision))

@staticmethod
def loads(pkg_ref): # TODO: change this default to validate only on end points
try:
tokens = pkg_ref.split(":", 1)
assert len(tokens) == 2
ref, pkg_id = tokens

ref = RecipeReference.loads(ref)

# timestamp
tokens = pkg_id.rsplit("%", 1)
text = tokens[0]
timestamp = float(tokens[1]) if len(tokens) == 2 else None

# revision
tokens = text.split("#", 1)
package_id = tokens[0]
revision = tokens[1] if len(tokens) == 2 else None

return PkgReference(ref, package_id, revision, timestamp)
except Exception:
raise ConanException(
f"{pkg_ref} is not a valid package reference, provide a reference"
f" in the form name/version[@user/channel:package_id]")
Loading

0 comments on commit a7d6d88

Please sign in to comment.