Skip to content

Commit f359099

Browse files
authored
Merge branch 'develop2' into pgi/ssh-runner
2 parents ba11f7c + c0ef44d commit f359099

File tree

8 files changed

+182
-21
lines changed

8 files changed

+182
-21
lines changed

.github/workflows/linux-tests.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,6 @@ jobs:
9696
linux_runner_tests:
9797
needs: build_container
9898
runs-on: ubuntu-latest
99-
container:
100-
image: ghcr.io/${{ github.repository_owner }}/conan-tests:${{ needs.build_container.outputs.image_tag }}
101-
options: --user conan
10299
strategy:
103100
matrix:
104101
# Use modern versions due to docker incompatibility with python <3.8
@@ -110,9 +107,9 @@ jobs:
110107
uses: actions/checkout@v4
111108

112109
- name: Set up Python ${{ matrix.python-version }}
113-
run: |
114-
pyenv global ${{ matrix.python-version }}
115-
python --version
110+
uses: actions/setup-python@v5
111+
with:
112+
python-version: ${{ matrix.python-version }}
116113

117114
- name: Cache pip
118115
uses: actions/cache@v4

conan/api/subapi/command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,4 @@ def get_runner(profile_host):
5959
except KeyError:
6060
raise ConanException(f"Invalid runner type '{runner_type}'. "
6161
f"Allowed values: {', '.join(runner_instances_map.keys())}")
62-
return runner_instance(profile_host.runner)
62+
return runner_instance

conan/internal/model/version_range.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,11 @@ def _parse_expression(expression):
8484

8585
operator = expression[0]
8686
if operator not in (">", "<", "^", "~", "="):
87-
operator = "="
87+
if expression[-1] == "*": # Handle patterns like "1.2.*"
88+
operator = "*"
89+
expression = expression[:-1]
90+
else:
91+
operator = "="
8892
index = 0
8993
else:
9094
index = 1
@@ -142,6 +146,9 @@ def valid(self, version, conf_resolve_prepreleases):
142146
elif condition.operator == "=":
143147
if not version == condition.version:
144148
return False
149+
elif condition.operator == "*":
150+
if not str(version).startswith(str(condition.version)):
151+
return False
145152
return True
146153

147154

conan/tools/files/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from conan.tools.files.files import load, save, mkdir, rmdir, rm, ftp_download, download, get, \
22
rename, chdir, unzip, replace_in_file, collect_libs, check_md5, check_sha1, check_sha256, \
3-
move_folder_contents
3+
move_folder_contents, chmod
44

55
from conan.tools.files.patches import patch, apply_conandata_patches, export_conandata_patches
66
from conan.tools.files.packager import AutoPackager

conan/tools/files/files.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import gzip
22
import os
3+
import stat
34
import platform
45
import shutil
56
import subprocess
@@ -263,6 +264,48 @@ def chdir(conanfile, newdir):
263264
os.chdir(old_path)
264265

265266

267+
def chmod(conanfile, path:str, read:bool=None, write:bool=None, execute:bool=None, recursive:bool=False):
268+
"""
269+
Change the permissions of a file or directory. The same as the Unix command chmod, but simplified.
270+
On Windows is limited to changing write permission only.
271+
272+
:param conanfile: The current recipe object. Always use ``self``.
273+
:param path: Path to the file or directory to change the permissions.
274+
:param read: If ``True``, the file or directory will have read permissions for owner user.
275+
:param write: If ``True``, the file or directory will have write permissions for owner user.
276+
:param execute: If ``True``, the file or directory will have execute permissions for owner user.
277+
:param recursive: If ``True``, the permissions will be applied
278+
"""
279+
if read is None and write is None and execute is None:
280+
raise ConanException("Could not change permission: At least one of the permissions should be set.")
281+
282+
if not os.path.exists(path):
283+
raise ConanException(f"Could not change permission: Path \"{path}\" does not exist.")
284+
285+
def _change_permission(it_path:str):
286+
mode = os.stat(it_path).st_mode
287+
permissions = [
288+
(read, stat.S_IRUSR),
289+
(write, stat.S_IWUSR),
290+
(execute, stat.S_IXUSR)
291+
]
292+
for enabled, mask in permissions:
293+
if enabled is None:
294+
continue
295+
elif enabled:
296+
mode |= mask
297+
else:
298+
mode &= ~mask
299+
os.chmod(it_path, mode)
300+
301+
if recursive:
302+
for root, _, files in os.walk(path):
303+
for file in files:
304+
_change_permission(os.path.join(root, file))
305+
else:
306+
_change_permission(path)
307+
308+
266309
def unzip(conanfile, filename, destination=".", keep_permissions=False, pattern=None,
267310
strip_root=False, extract_filter=None):
268311
"""

test/functional/command/runner_test.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from conan.test.assets.sources import gen_function_h, gen_function_cpp
99

1010

11-
def docker_skip(test_image=None):
11+
def docker_skip(test_image='ubuntu:22.04'):
1212
try:
1313
try:
1414
docker_client = docker.from_env()
@@ -38,7 +38,7 @@ def dockerfile_path(name=None):
3838

3939

4040
@pytest.mark.docker_runner
41-
@pytest.mark.skipif(docker_skip('ubuntu:22.04'), reason="Only docker running")
41+
@pytest.mark.skipif(docker_skip(), reason="Only docker running")
4242
def test_create_docker_runner_cache_shared():
4343
"""
4444
Tests the ``conan create . ``
@@ -82,7 +82,7 @@ def test_create_docker_runner_cache_shared():
8282

8383

8484
@pytest.mark.docker_runner
85-
@pytest.mark.skipif(docker_skip('ubuntu:22.04'), reason="Only docker running")
85+
@pytest.mark.skipif(docker_skip(), reason="Only docker running")
8686
def test_create_docker_runner_cache_shared_profile_from_cache():
8787
"""
8888
Tests the ``conan create . ``
@@ -126,7 +126,7 @@ def test_create_docker_runner_cache_shared_profile_from_cache():
126126

127127

128128
@pytest.mark.docker_runner
129-
@pytest.mark.skipif(docker_skip('ubuntu:22.04'), reason="Only docker running")
129+
@pytest.mark.skipif(docker_skip(), reason="Only docker running")
130130
def test_create_docker_runner_cache_shared_profile_folder():
131131
"""
132132
Tests the ``conan create . ``
@@ -170,7 +170,7 @@ def test_create_docker_runner_cache_shared_profile_folder():
170170
assert "Removing container" in client.out
171171

172172
@pytest.mark.docker_runner
173-
@pytest.mark.skipif(docker_skip('ubuntu:22.04'), reason="Only docker running")
173+
@pytest.mark.skipif(docker_skip(), reason="Only docker running")
174174
def test_create_docker_runner_dockerfile_folder_path():
175175
"""
176176
Tests the ``conan create . ``
@@ -241,7 +241,7 @@ def test_create_docker_runner_dockerfile_folder_path():
241241

242242

243243
@pytest.mark.docker_runner
244-
@pytest.mark.skipif(docker_skip('ubuntu:22.04'), reason="Only docker running")
244+
@pytest.mark.skipif(docker_skip(), reason="Only docker running")
245245
def test_create_docker_runner_profile_default_folder():
246246
"""
247247
Tests the ``conan create . ``
@@ -287,7 +287,7 @@ def test_create_docker_runner_profile_default_folder():
287287

288288

289289
@pytest.mark.docker_runner
290-
@pytest.mark.skipif(docker_skip('ubuntu:22.04'), reason="Only docker running")
290+
@pytest.mark.skipif(docker_skip(), reason="Only docker running")
291291
def test_create_docker_runner_dockerfile_file_path():
292292
"""
293293
Tests the ``conan create . ``
@@ -332,7 +332,7 @@ def test_create_docker_runner_dockerfile_file_path():
332332

333333

334334
@pytest.mark.docker_runner
335-
@pytest.mark.skipif(docker_skip('ubuntu:22.04'), reason="Only docker running")
335+
@pytest.mark.skipif(docker_skip(), reason="Only docker running")
336336
@pytest.mark.parametrize("build_type,shared", [("Release", False), ("Debug", True)])
337337
def test_create_docker_runner_with_ninja(build_type, shared):
338338
conanfile = textwrap.dedent("""
@@ -397,7 +397,7 @@ def package(self):
397397
assert "main: {}!".format(build_type) in client.out
398398

399399
@pytest.mark.docker_runner
400-
@pytest.mark.skipif(docker_skip('ubuntu:22.04'), reason="Only docker running")
400+
@pytest.mark.skipif(docker_skip(), reason="Only docker running")
401401
def test_create_docker_runner_from_configfile():
402402
"""
403403
Tests the ``conan create . ``
@@ -452,7 +452,7 @@ def test_create_docker_runner_from_configfile():
452452

453453

454454
@pytest.mark.docker_runner
455-
@pytest.mark.skipif(docker_skip('ubuntu:22.04'), reason="Only docker running")
455+
@pytest.mark.skipif(docker_skip(), reason="Only docker running")
456456
def test_create_docker_runner_from_configfile_with_args():
457457
"""
458458
Tests the ``conan create . ``
@@ -516,7 +516,7 @@ def test_create_docker_runner_from_configfile_with_args():
516516
docker_client.networks.get("my-network").remove()
517517

518518
@pytest.mark.docker_runner
519-
@pytest.mark.skipif(docker_skip('ubuntu:22.04'), reason="Only docker running")
519+
@pytest.mark.skipif(docker_skip(), reason="Only docker running")
520520
def test_create_docker_runner_default_build_profile():
521521
"""
522522
Tests the ``conan create . ``
@@ -592,7 +592,7 @@ def test_create_docker_runner_profile_composition():
592592

593593

594594
@pytest.mark.docker_runner
595-
@pytest.mark.skipif(docker_skip('ubuntu:22.04'), reason="Only docker running")
595+
@pytest.mark.skipif(docker_skip(), reason="Only docker running")
596596
def test_create_docker_runner_in_subfolder():
597597
client = TestClient()
598598
conanfile = textwrap.dedent("""

test/unittests/model/version/test_version_range.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# tilde
1616
['~2.5', [[['>=', '2.5-'], ['<', '2.6-']]], ["2.5.0", "2.5.3"], ["2.7", "2.6.1"]],
1717
['~2.5.1', [[['>=', '2.5.1-'], ['<', '2.6.0-']]], ["2.5.1", "2.5.3"], ["2.5", "2.6.1"]],
18+
['~2.5.1.3', [[['>=', '2.5.1.3-'], ['<', '2.6.0-']]], ["2.5.1.4", "2.5.3"], ["2.5.1", "2.6.1"]],
1819
['~1', [[['>=', '1-'], ['<', '2-']]], ["1.3", "1.8.1"], ["0.8", "2.2"]],
1920
# caret
2021
['^1.2', [[['>=', '1.2-'], ['<', '2.0-']]], ["1.2.1", "1.51"], ["1", "2", "2.0.1"]],
@@ -26,6 +27,9 @@
2627
# Any
2728
['*', [[[">=", "0.0.0-"]]], ["1.0", "a.b"], []],
2829
['', [[[">=", "0.0.0-"]]], ["1.0", "a.b"], []],
30+
# Patterns
31+
['1.2.3.*', [[["*", "1.2.3."]]], ["1.2.3.1", "1.2.3.A"], ["1.2.3", "1.2.31", "1.2.4"]],
32+
['1.2.3+4.5.*', [[["*", "1.2.3+4.5."]]], ["1.2.3+4.5.6"], ["1.2.3+4.7"]],
2933
# Unions
3034
['1.0.0 || 2.1.3', [[["=", "1.0.0"]], [["=", "2.1.3"]]], ["1.0.0", "2.1.3"], ["2", "1.0.1"]],
3135
['>1 <2.0 || ^3.2 ', [[['>', '1'], ['<', '2.0-']],
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import os
2+
import platform
3+
import stat
4+
import pytest
5+
6+
from conan.errors import ConanException
7+
from conan.tools.files import chmod
8+
from conan.test.utils.mocks import ConanFileMock
9+
from conan.test.utils.tools import temp_folder, save_files
10+
11+
12+
@pytest.mark.skipif(platform.system() == "Windows", reason="validate full permissions only in Unix")
13+
@pytest.mark.parametrize("read,write,execute,expected", [
14+
(True, True, True, 0o700),
15+
(False, True, False, 0o200),
16+
(False, False, True, 0o100),
17+
(True, False, True, 0o500),
18+
(True, True, False, 0o600),
19+
(True, False, False, 0o400),
20+
(False, False, False, 0o000),])
21+
def test_chmod_single_file(read, write, execute, expected):
22+
"""
23+
The chmod should be able to change the permissions of a single file.
24+
"""
25+
tmp = temp_folder()
26+
save_files(tmp, {"file.txt": "foobar"})
27+
file_path = os.path.join(tmp, "file.txt")
28+
os.chmod(file_path, 0o000)
29+
conanfile = ConanFileMock()
30+
chmod(conanfile, file_path, read=read, write=write, execute=execute, recursive=False)
31+
file_mode = os.stat(file_path).st_mode
32+
assert stat.S_IMODE(file_mode) == expected
33+
34+
35+
@pytest.mark.skipif(platform.system() == "Windows", reason="validate full permissions only in Unix")
36+
@pytest.mark.parametrize("read,write,execute,expected", [
37+
(True, True, True, 0o700),
38+
(False, True, False, 0o200),
39+
(False, False, True, 0o100),
40+
(True, False, True, 0o500),
41+
(True, True, False, 0o600),
42+
(True, False, False, 0o400),
43+
(False, False, False, 0o000),])
44+
def test_chmod_recursive(read, write, execute, expected):
45+
"""
46+
The chmod should be able to change the permissions of all files in a folder when recursive is set to True.
47+
"""
48+
tmp = temp_folder()
49+
files = {"foobar/qux/file.txt": "foobar",
50+
"foobar/file.txt": "qux",
51+
"foobar/foo/file.txt": "foobar"}
52+
save_files(tmp, files)
53+
folder_path = os.path.join(tmp, "foobar")
54+
for file in files.keys():
55+
file_path = os.path.join(tmp, file)
56+
os.chmod(file_path, 0o000)
57+
conanfile = ConanFileMock()
58+
chmod(conanfile, folder_path, read=read, write=write, execute=execute, recursive=True)
59+
for file in files.keys():
60+
file_mode = os.stat(os.path.join(tmp, file)).st_mode
61+
assert stat.S_IMODE(file_mode) == expected
62+
63+
64+
@pytest.mark.skipif(platform.system() == "Windows", reason="Validate default permissions only in Unix")
65+
def test_chmod_default_values():
66+
"""
67+
When not passing a permission parameter, chmod should not change the specific permission.
68+
"""
69+
tmp = temp_folder()
70+
save_files(tmp, {"file.txt": "foobar"})
71+
file_path = os.path.join(tmp, "file.txt")
72+
os.chmod(file_path, 0o111)
73+
conanfile = ConanFileMock()
74+
chmod(conanfile, file_path, read=True)
75+
file_mode = os.stat(file_path).st_mode
76+
assert stat.S_IMODE(file_mode) == 0o511
77+
78+
79+
def test_missing_permission_arguments():
80+
"""
81+
The chmod should raise an exception if no new permission is provided.
82+
"""
83+
conanfile = ConanFileMock()
84+
with pytest.raises(ConanException) as error:
85+
chmod(conanfile, "invalid_path")
86+
assert 'Could not change permission: At least one of the permissions should be set.' in str(error.value)
87+
88+
89+
def test_invalid_path():
90+
"""
91+
The chmod should raise an exception if the path does not exist.
92+
"""
93+
conanfile = ConanFileMock()
94+
with pytest.raises(ConanException) as error:
95+
chmod(conanfile, "invalid_path", read=True, write=True, execute=True, recursive=False)
96+
assert 'Could not change permission: Path "invalid_path" does not exist.' in str(error.value)
97+
98+
99+
@pytest.mark.skipif(platform.system() != "Windows", reason="Validate read-only permissions only in Windows")
100+
def test_chmod_windows():
101+
"""
102+
The chmod should be able to change read-only state in Windows.
103+
"""
104+
tmp = temp_folder()
105+
save_files(tmp, {"file.txt": "foobar"})
106+
file_path = os.path.join(tmp, "file.txt")
107+
os.chmod(file_path, 0o000)
108+
conanfile = ConanFileMock()
109+
chmod(conanfile, file_path, read=True, write=True, execute=True, recursive=False)
110+
assert os.access(file_path, os.W_OK)

0 commit comments

Comments
 (0)