summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Cristian Sarbu <paul.cristian.sarbu@huawei.com>2025-02-18 17:33:15 +0100
committerPaul Cristian Sarbu <paul.cristian.sarbu@huawei.com>2025-02-20 15:33:53 +0100
commit2adc4915d42547fd71e4d8983dc50e33df251b53 (patch)
tree47a5f415fd19a9236f684269a0d751d3648587f8
parente749a97621d445de5e0dec89ab840dd61839d872 (diff)
downloadjustbuild-2adc4915d42547fd71e4d8983dc50e33df251b53.tar.gz
just-lock: Support special pragma for plain imports
Marking a source repository 'as plain' means that the whole source repository tree will get imported as a repository type corresponding to the source type. In this case, additional pragmas than those supported by the inndividual imports might need to be set. Solve this by supporting the just-mr-style 'pragma' field also in the source description, for all sources also accepting the 'as plain' field. Currently support only the 'special' pragma. Document change and add test for plain imports that checks this feature.
-rwxr-xr-xbin/just-lock.py81
-rw-r--r--doc/future-designs/just-lock.md28
-rw-r--r--share/man/just-lock-config.5.md20
-rw-r--r--test/end-to-end/just-lock/TARGETS11
-rw-r--r--test/end-to-end/just-lock/plain-imports.sh137
5 files changed, 263 insertions, 14 deletions
diff --git a/bin/just-lock.py b/bin/just-lock.py
index 1639a656..ddbbd21f 100755
--- a/bin/just-lock.py
+++ b/bin/just-lock.py
@@ -745,21 +745,27 @@ def rewrite_file_repo(repo: Json, remote_type: str, remote_stub: Dict[str, Any],
fail("Unsupported remote type!")
-def update_pragmas(repo: Json, pragma: Json) -> Json:
- """Update the description with any input-provided "absent" and "to_git"
- pragmas, as needed:
- - for all repositories, merge the "absent" pragma
- - for "file"-type repositories, merge the "to_git" pragma"""
+def update_pragmas(repo: Json, import_pragma: Json,
+ pragma_special: Optional[str]) -> Json:
+ """Update the description with any input-provided pragmas:
+ - for all repositories, merge with import-level "absent" pragma
+ - for "file"-type repositories, merge with import-level "to_git" pragma
+ - for all repositories, overwrite with source-level "special" pragma."""
existing: Json = dict(repo.get("pragma", {})) # operate on copy
# all repos support "absent pragma"
- absent: bool = existing.get("absent", False) or pragma.get("absent", False)
+ absent: bool = existing.get("absent", False) or import_pragma.get(
+ "absent", False)
if absent:
existing["absent"] = True
# support "to_git" pragma for "file"-type repos
if repo.get("type") == "file":
- to_git = existing.get("to_git", False) or pragma.get("to_git", False)
+ to_git = existing.get("to_git", False) or import_pragma.get(
+ "to_git", False)
if to_git:
existing["to_git"] = True
+ # all repos get the "special" pragma overwritten, if provided
+ if pragma_special is not None:
+ existing["special"] = pragma_special
# all other pragmas as kept; if no pragma was set, do not set any
if existing:
repo = dict(repo, **{"pragma": existing})
@@ -767,8 +773,9 @@ def update_pragmas(repo: Json, pragma: Json) -> Json:
def rewrite_repo(repo_spec: Json, *, remote_type: str,
- remote_stub: Dict[str, Any], assign: Json, pragma: Json,
- as_layer: bool, fail_context: str) -> Json:
+ remote_stub: Dict[str, Any], assign: Json, import_pragma: Json,
+ pragma_special: Optional[str], as_layer: bool,
+ fail_context: str) -> Json:
"""Rewrite description of imported repositories."""
new_spec: Json = {}
repo = repo_spec.get("repository", {})
@@ -789,7 +796,7 @@ def rewrite_repo(repo_spec: Json, *, remote_type: str,
repo = dict(repo, **{"repo": assign[target]})
# update pragmas, as needed
if isinstance(repo, dict):
- repo = update_pragmas(repo, pragma)
+ repo = update_pragmas(repo, import_pragma, pragma_special)
new_spec["repository"] = repo
# rewrite other roots and bindings, if actually needed to be imported
if not as_layer:
@@ -811,8 +818,8 @@ def rewrite_repo(repo_spec: Json, *, remote_type: str,
def handle_import(remote_type: str, remote_stub: Dict[str, Any],
- repo_desc: Json, core_repos: Json, foreign_config: Json, *,
- fail_context: str) -> Json:
+ repo_desc: Json, core_repos: Json, foreign_config: Json,
+ pragma_special: Optional[str], *, fail_context: str) -> Json:
"""General handling of repository import from a foreign config."""
fail_context += "While handling import from remote type \"%s\"\n" % (
remote_type, )
@@ -885,7 +892,8 @@ def handle_import(remote_type: str, remote_stub: Dict[str, Any],
remote_type=remote_type,
remote_stub=remote_stub,
assign=total_assign,
- pragma=pragma,
+ import_pragma=pragma,
+ pragma_special=pragma_special,
as_layer=False,
fail_context=fail_context)
for repo in extra_imports:
@@ -893,7 +901,8 @@ def handle_import(remote_type: str, remote_stub: Dict[str, Any],
remote_type=remote_type,
remote_stub=remote_stub,
assign=total_assign,
- pragma=pragma,
+ import_pragma=pragma,
+ pragma_special=pragma_special,
as_layer=True,
fail_context=fail_context)
@@ -1048,6 +1057,16 @@ def import_from_git(core_repos: Json, imports_entry: Json) -> Json:
"Expected field \"config\" to be a string, but found:\n%r" %
(json.dumps(foreign_config_file, indent=2), ))
+ pragma_special: Optional[str] = imports_entry.get("pragma",
+ {}).get("special", None)
+ if pragma_special is not None and not isinstance(pragma_special, str):
+ fail(fail_context +
+ "Expected pragma \"special\" to be a string, but found:\n%r" %
+ (json.dumps(pragma_special, indent=2), ))
+ if not as_plain:
+ # only enabled if as_plain is true
+ pragma_special = None
+
# Fetch the source Git repository
srcdir, remote_stub, to_clean_up = git_checkout(url,
branch,
@@ -1093,6 +1112,7 @@ def import_from_git(core_repos: Json, imports_entry: Json) -> Json:
repo_entry,
core_repos,
foreign_config,
+ pragma_special,
fail_context=fail_context)
# Clean up local fetch
@@ -1141,6 +1161,16 @@ def import_from_file(core_repos: Json, imports_entry: Json) -> Json:
"Expected field \"config\" to be a string, but found:\n%r" %
(json.dumps(foreign_config_file, indent=2), ))
+ pragma_special: Optional[str] = imports_entry.get("pragma",
+ {}).get("special", None)
+ if pragma_special is not None and not isinstance(pragma_special, str):
+ fail(fail_context +
+ "Expected pragma \"special\" to be a string, but found:\n%r" %
+ (json.dumps(pragma_special, indent=2), ))
+ if not as_plain:
+ # only enabled if as_plain is true
+ pragma_special = None
+
# Read in the foreign config file
if foreign_config_file:
foreign_config_file = os.path.join(path, foreign_config_file)
@@ -1184,6 +1214,7 @@ def import_from_file(core_repos: Json, imports_entry: Json) -> Json:
repo_entry,
core_repos,
foreign_config,
+ pragma_special,
fail_context=fail_context)
return core_repos
@@ -1418,6 +1449,16 @@ def import_from_archive(core_repos: Json, imports_entry: Json) -> Json:
"Expected field \"config\" to be a string, but found:\n%r" %
(json.dumps(foreign_config_file, indent=2), ))
+ pragma_special: Optional[str] = imports_entry.get("pragma",
+ {}).get("special", None)
+ if pragma_special is not None and not isinstance(pragma_special, str):
+ fail(fail_context +
+ "Expected pragma \"special\" to be a string, but found:\n%r" %
+ (json.dumps(pragma_special, indent=2), ))
+ if not as_plain:
+ # only enabled if as_plain is true
+ pragma_special = None
+
# Fetch archive to local CAS and unpack
srcdir, remote_stub, to_clean_up = archive_checkout(
fetch,
@@ -1466,6 +1507,7 @@ def import_from_archive(core_repos: Json, imports_entry: Json) -> Json:
repo_entry,
core_repos,
foreign_config,
+ pragma_special,
fail_context=fail_context)
# Clean up local fetch
@@ -1615,6 +1657,16 @@ def import_from_git_tree(core_repos: Json, imports_entry: Json) -> Json:
"Expected field \"config\" to be a string, but found:\n%r" %
(json.dumps(foreign_config_file, indent=2), ))
+ pragma_special: Optional[str] = imports_entry.get("pragma",
+ {}).get("special", None)
+ if pragma_special is not None and not isinstance(pragma_special, str):
+ fail(fail_context +
+ "Expected pragma \"special\" to be a string, but found:\n%r" %
+ (json.dumps(pragma_special, indent=2), ))
+ if not as_plain:
+ # only enabled if as_plain is true
+ pragma_special = None
+
# Fetch the Git tree
srcdir, remote_stub, to_clean_up = git_tree_checkout(
command=cast(List[str], command_gen if command is None else command),
@@ -1661,6 +1713,7 @@ def import_from_git_tree(core_repos: Json, imports_entry: Json) -> Json:
repo_entry,
core_repos,
foreign_config,
+ pragma_special,
fail_context=fail_context)
# Clean up local fetch
diff --git a/doc/future-designs/just-lock.md b/doc/future-designs/just-lock.md
index 151625c1..61e3c900 100644
--- a/doc/future-designs/just-lock.md
+++ b/doc/future-designs/just-lock.md
@@ -230,6 +230,12 @@ The type of a _source_ is defined by the string value of the mandatory subfield
that will be used in the resulting repository description corresponding to any
imported `"file"`-type repositories (see `just-import-git`).
+ If `"as plain": true`, any provided `"special"` key for the `"pragma"` field
+ in the source description is unconditionally set in the imported repositories,
+ superseding any other config- or import-level treatment of pragmas during the
+ import. Note that `"as plain": true` results in only one repository
+ (containing the whole source repository tree) being imported.
+
Proposed format:
``` jsonc
{ "source": "git"
@@ -254,6 +260,7 @@ The type of a _source_ is defined by the string value of the mandatory subfield
, "inherit env": [...] // optional; corresponds to `inherit_env` var (option --inherit-env)
, "config": "<foreign_repos.json>" // optional; corresponds to `foreign_repository_config` var (option -R)
, "as plain": false // optional; corresponds to `plain` var (option --plain)
+ , "pragma": {"special": "<value>"} // optional; only considered if `"as plain": true`
}
```
@@ -268,6 +275,12 @@ The type of a _source_ is defined by the string value of the mandatory subfield
one can also set the `"to_git": true` pragma with a corresponding entry in the
usual `"pragma"` field.
+ If `"as plain": true`, any provided `"special"` key for the `"pragma"` field
+ in the source description is unconditionally set in the imported repositories,
+ superseding any other config- or import-level treatment of pragmas during the
+ import. Note that `"as plain": true` results in only one repository
+ (containing the whole source repository tree) being imported.
+
Proposed format:
``` jsonc
{ "source": "file"
@@ -289,6 +302,7 @@ The type of a _source_ is defined by the string value of the mandatory subfield
, "path": "<source/repo/path>" // mandatory
, "config": "<foreign_repos.json>" // optional; corresponds to `foreign_repository_config` var (option -R)
, "as plain": false // optional; corresponds to `plain` var (option --plain)
+ , "pragma": {"special": "<value>"} // optional; only considered if `"as plain": true`
}
```
@@ -301,6 +315,12 @@ The type of a _source_ is defined by the string value of the mandatory subfield
A field `"subdir"` is provided to account for the fact that source repository
root often is not the root directory of the unpacked archive.
+ If `"as plain": true`, any provided `"special"` key for the `"pragma"` field
+ in the source description is unconditionally set in the imported repositories,
+ superseding any other config- or import-level treatment of pragmas during the
+ import. Note that `"as plain": true` results in only one repository
+ (containing the whole source repository tree) being imported.
+
Proposed format:
``` jsonc
{ "source": "archive"
@@ -327,6 +347,7 @@ The type of a _source_ is defined by the string value of the mandatory subfield
, "sha512": "<HASH>" // optional checksum; if given, will be checked
, "config": "<foreign_repos.json>" // optional; corresponds to `foreign_repository_config` var (option -R)
, "as plain": false // optional; corresponds to `plain` var (option --plain)
+ , "pragma": {"special": "<value>"} // optional; only considered if `"as plain": true`
}
```
@@ -358,6 +379,12 @@ The type of a _source_ is defined by the string value of the mandatory subfield
such repositories will be translated to appropriate `"git tree"`-type
repositories in the output configuration.
+ If `"as plain": true`, any provided `"special"` key for the `"pragma"` field
+ in the source description is unconditionally set in the imported repositories,
+ superseding any other config- or import-level treatment of pragmas during the
+ import. Note that `"as plain": true` results in only one repository
+ (containing the whole source repository tree) being imported.
+
Proposed format:
``` jsonc
{ "source": "git tree"
@@ -381,6 +408,7 @@ The type of a _source_ is defined by the string value of the mandatory subfield
, "config": "<foreign_repos.json>" // optional; corresponds to `foreign_repository_config` var (option -R)
// searched for in the "subdir" tree
, "as plain": false // optional; corresponds to `plain` var (option --plain)
+ , "pragma": {"special": "<value>"} // optional; only considered if `"as plain": true`
}
```
diff --git a/share/man/just-lock-config.5.md b/share/man/just-lock-config.5.md
index 5ae901fb..3e71fd30 100644
--- a/share/man/just-lock-config.5.md
+++ b/share/man/just-lock-config.5.md
@@ -107,6 +107,11 @@ The following fields are supported:
Git repository does not have a repository configuration or should be imported
as-is, without dependencies. This entry is optional.
+ - *`"pragma"`* has as value a JSON object. If `"as plain"` evaluates to `true`,
+ if a pragma object with key `"special"` is provided, it will unconditionally
+ be forwarded to the `"pragma"` object of the repository being imported for
+ this source. This entry is optional.
+
- *`"config"`* has a string value defining the relative path of the foreign
repository configuration file to be considered from the Git repository. This
entry is optional. If not provided and the `"as plain"` field does not
@@ -136,6 +141,11 @@ The following fields are supported:
Git repository does not have a repository configuration or should be imported
as-is, without dependencies. This entry is optional.
+ - *`"pragma"`* has as value a JSON object. If `"as plain"` evaluates to `true`,
+ if a pragma object with key `"special"` is provided, it will unconditionally
+ be forwarded to the `"pragma"` object of the repository being imported for
+ this source. This entry is optional.
+
- *`"config"`* has a string value defining the relative path of the foreign
repository configuration file to be considered from the Git repository. This
entry is optional. If not provided and the `"as plain"` field does not
@@ -190,6 +200,11 @@ The following fields are supported:
archived repository does not have a configuration file or should be imported
as-is, without dependencies. This entry is optional.
+ - *`"pragma"`* has as value a JSON object. If `"as plain"` evaluates to `true`,
+ if a pragma object with key `"special"` is provided, it will unconditionally
+ be forwarded to the `"pragma"` object of the repository being imported for
+ this source. This entry is optional.
+
- *`"config"`* has a string value defining the relative path of the foreign
repository configuration file to be considered from the unpacked archive
root. This entry is optional. If not provided and the `"as plain"` field does
@@ -239,6 +254,11 @@ The following fields are supported:
Git repository does not have a repository configuration or should be imported
as-is, without dependencies. This entry is optional.
+ - *`"pragma"`* has as value a JSON object. If `"as plain"` evaluates to `true`,
+ if a pragma object with key `"special"` is provided, it will unconditionally
+ be forwarded to the `"pragma"` object of the repository being imported for
+ this source. This entry is optional.
+
- *`"config"`* has a string value defining the relative path of the foreign
repository configuration file to be considered from the Git repository. This
entry is optional. If not provided and the `"as plain"` field does not
diff --git a/test/end-to-end/just-lock/TARGETS b/test/end-to-end/just-lock/TARGETS
index 38c26ad3..ab3bc758 100644
--- a/test/end-to-end/just-lock/TARGETS
+++ b/test/end-to-end/just-lock/TARGETS
@@ -78,6 +78,16 @@
, ["end-to-end", "lock-tool-under-test"]
]
}
+, "plain-imports":
+ { "type": ["@", "rules", "shell/test", "script"]
+ , "name": ["plain-imports"]
+ , "test": ["plain-imports.sh"]
+ , "deps":
+ [ ["", "mr-tool-under-test"]
+ , ["", "tool-under-test"]
+ , ["end-to-end", "lock-tool-under-test"]
+ ]
+ }
, "TESTS":
{ "type": ["@", "rules", "test", "suite"]
, "arguments_config": ["TEST_BOOTSTRAP_JUST_MR"]
@@ -95,6 +105,7 @@
, "file-imports"
, "archive-imports"
, "git-tree-imports"
+ , "plain-imports"
]
}
]
diff --git a/test/end-to-end/just-lock/plain-imports.sh b/test/end-to-end/just-lock/plain-imports.sh
new file mode 100644
index 00000000..7935f1df
--- /dev/null
+++ b/test/end-to-end/just-lock/plain-imports.sh
@@ -0,0 +1,137 @@
+#!/bin/sh
+# Copyright 2025 Huawei Cloud Computing Technology Co., Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+set -eu
+
+readonly JUST_LOCK="${PWD}/bin/lock-tool-under-test"
+readonly JUST="${PWD}/bin/tool-under-test"
+readonly JUST_MR="${PWD}/bin/mr-tool-under-test"
+readonly LBR="${TEST_TMPDIR}/local-build-root"
+readonly LBR_PLAIN="${TEST_TMPDIR}/local-build-root-plain"
+readonly OUT="${TEST_TMPDIR}/build-output"
+readonly OUT_PLAIN="${TEST_TMPDIR}/build-output-plain"
+readonly REPO_DIRS="${TEST_TMPDIR}/repos"
+readonly WRKDIR="${PWD}/work"
+
+mkdir -p "${REPO_DIRS}/foo/inner"
+cd "${REPO_DIRS}/foo"
+touch ROOT
+cat > repos.json <<'EOF'
+{ "repositories":
+ { "":
+ { "repository":
+ { "type": "file"
+ , "path": "inner"
+ }
+ }
+ }
+}
+EOF
+# add resolvable linked TARGETS file
+ln -s inner/linked_TARGETS TARGETS
+cat > inner/linked_TARGETS <<'EOF'
+{ "": {"type": "file_gen", "name": "foo.txt", "data": "LINK"}}
+EOF
+# add inner TARGETS file shadowed by the linked one
+cat > inner/TARGETS << 'EOF'
+{ "": {"type": "file_gen", "name": "foo.txt", "data": "INNER"}}
+EOF
+
+mkdir -p "${WRKDIR}"
+cd "${WRKDIR}"
+touch ROOT
+cat > TARGETS <<'EOF'
+{ "":
+ { "type": "generic"
+ , "cmds": ["cat foo.txt > out.txt"]
+ , "outs": ["out.txt"]
+ , "deps": [["@", "foo", "", ""]]
+ }
+}
+EOF
+
+
+echo === Check normal import ===
+
+cat > repos.in.json <<EOF
+{ "repositories":
+ { "":
+ { "repository": {"type": "file", "path": "."}
+ , "bindings": {"foo": "foo"}
+ }
+ }
+ , "imports":
+ [ { "source": "file"
+ , "repos": [{"alias": "foo", "pragma": {"to_git": true}}]
+ , "path": "${REPO_DIRS}/foo"
+ }
+ ]
+}
+EOF
+cat repos.in.json
+
+echo
+"${JUST_LOCK}" -C repos.in.json -o repos.json --local-build-root "${LBR}" 2>&1
+cat repos.json
+echo
+# Check pragmas: "to_git" is kept
+[ $(jq -r '.repositories.foo.repository.pragma.to_git' repos.json) = true ]
+# Check that the subdir is taken as expected
+"${JUST_MR}" -L '["env", "PATH='"${PATH}"'"]' --norc --just "${JUST}" \
+ --local-build-root "${LBR}" install -o "${OUT}" 2>&1
+echo
+cat "${OUT}/out.txt"
+echo
+grep -q INNER "${OUT}/out.txt"
+
+
+echo == Check plain import ===
+
+cat > repos.in.json <<EOF
+{ "repositories":
+ { "":
+ { "repository": {"type": "file", "path": "."}
+ , "bindings": {"foo": "foo"}
+ }
+ }
+ , "imports":
+ [ { "source": "file"
+ , "repos": [{"alias": "foo", "pragma": {"to_git": true}}]
+ , "path": "${REPO_DIRS}/foo"
+ , "as plain": true
+ , "pragma": {"special": "resolve-completely"}
+ }
+ ]
+}
+EOF
+cat repos.in.json
+
+echo
+"${JUST_LOCK}" -C repos.in.json -o repos.json --local-build-root "${LBR_PLAIN}" 2>&1
+cat repos.json
+echo
+# Check pragmas: "to_git" is kept, "special" is unconditionally set
+[ $(jq -r '.repositories.foo.repository.pragma.special' repos.json) = "resolve-completely" ]
+[ $(jq -r '.repositories.foo.repository.pragma.to_git' repos.json) = true ]
+# Check the symlink gets resolved as expected
+"${JUST_MR}" -L '["env", "PATH='"${PATH}"'"]' --norc --just "${JUST}" \
+ --local-build-root "${LBR_PLAIN}" install -o "${OUT_PLAIN}" 2>&1
+echo
+cat "${OUT_PLAIN}/out.txt"
+echo
+grep -q LINK "${OUT_PLAIN}/out.txt"
+
+echo "OK"