Skip to content

Commit 36a34cb

Browse files
authored
Address bundling order issue
The order in which packs are bundled didn't consider dependencies between packs. This could lead to failures when a bundle being added to the bundle couldn't resolve its dependencies from the bundle because they weren't added yet. This fix introduces a dependency graph that orders the workspace packs and impacted bundle packs.
2 parents c5b0445 + 60f91f6 commit 36a34cb

File tree

26 files changed

+587
-368
lines changed

26 files changed

+587
-368
lines changed

codeql_bundle/cli.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
# Add the parent directory to the path if this module is run directly (i.e. not imported)
2+
# This is necessary to support both the Poetry script invocation and the direct invocation.
3+
if not __package__ and __name__ == "__main__":
4+
import sys
5+
from pathlib import Path
6+
7+
sys.path.append(str(Path(__file__).parent.parent))
8+
__package__ = Path(__file__).parent.name
9+
110
import click
211
from pathlib import Path
312
from codeql_bundle.helpers.codeql import CodeQLException
@@ -8,7 +17,6 @@
817

918
logger = logging.getLogger(__name__)
1019

11-
1220
@click.command()
1321
@click.option(
1422
"-b",
@@ -71,32 +79,32 @@ def main(
7179
try:
7280
bundle = CustomBundle(bundle_path, workspace)
7381
logger.info(f"Looking for CodeQL packs in workspace {workspace}")
74-
packs_in_workspace = bundle.codeql.pack_ls(workspace)
82+
packs_in_workspace = bundle.getCodeQLPacks()
7583
logger.info(
76-
f"Found the CodeQL packs: {','.join(map(lambda p: p.name, packs_in_workspace))}"
84+
f"Found the CodeQL packs: {','.join(map(lambda p: p.config.name, packs_in_workspace))}"
7785
)
7886

7987
if len(packs) > 0:
8088
selected_packs = [
8189
available_pack
8290
for available_pack in packs_in_workspace
83-
if available_pack.name in packs
91+
if available_pack.config.name in packs
8492
]
8593
else:
8694
selected_packs = packs_in_workspace
8795

8896
logger.info(
89-
f"Considering the following CodeQL packs for inclusion in the custom bundle: {','.join(map(lambda p: p.name, selected_packs))}"
97+
f"Considering the following CodeQL packs for inclusion in the custom bundle: {','.join(map(lambda p: p.config.name, selected_packs))}"
9098
)
91-
missing_packs = set(packs) - {pack.name for pack in selected_packs}
99+
missing_packs = set(packs) - {pack.config.name for pack in selected_packs}
92100
if len(missing_packs) > 0:
93101
logger.fatal(
94102
f"The provided CodeQL workspace doesn't contain the provided packs '{','.join(missing_packs)}'",
95103
)
96104
sys.exit(1)
97105

98106
logger.info(
99-
f"Adding the packs {','.join(map(lambda p: p.name, selected_packs))} to the custom bundle."
107+
f"Adding the packs {','.join(map(lambda p: p.config.name, selected_packs))} and its workspace dependencies to the custom bundle."
100108
)
101109
bundle.add_packs(*selected_packs)
102110
logger.info(f"Bundling custom bundle at {output}")

codeql_bundle/helpers/bundle.py

Lines changed: 381 additions & 316 deletions
Large diffs are not rendered by default.

codeql_bundle/helpers/codeql.py

Lines changed: 35 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from typing import Dict, Any, Iterable, Self, Optional, List
66
import yaml
77
from dataclasses import dataclass, fields, field
8-
from enum import Enum
98
import logging
109

1110
logger = logging.getLogger(__name__)
@@ -16,7 +15,7 @@ class CodeQLException(Exception):
1615

1716

1817
@dataclass(kw_only=True, frozen=True, eq=True)
19-
class CodeQLPack:
18+
class CodeQLPackConfig:
2019
library: bool = False
2120
name: str
2221
version: Version = Version("0.0.0")
@@ -53,25 +52,18 @@ def get_pack_name(self) -> str:
5352
def __hash__(self):
5453
return hash(f"{self.name}@{str(self.version)}")
5554

56-
57-
class CodeQLPackKind(Enum):
58-
QUERY_PACK = 1
59-
LIBRARY_PACK = 2
60-
CUSTOMIZATION_PACK = 3
61-
62-
6355
@dataclass(kw_only=True, frozen=True, eq=True)
64-
class ResolvedCodeQLPack(CodeQLPack):
56+
class CodeQLPack:
6557
path: Path
66-
kind: CodeQLPackKind
67-
68-
def __hash__(self):
69-
return CodeQLPack.__hash__(self)
58+
config: CodeQLPackConfig
7059

60+
def __hash__(self) -> int:
61+
return hash(f"{self.path}")
7162

7263
class CodeQL:
7364
def __init__(self, codeql_path: Path):
7465
self.codeql_path = codeql_path
66+
self._version = None
7567

7668
def _exec(self, command: str, *args: str) -> subprocess.CompletedProcess[str]:
7769
logger.debug(
@@ -80,16 +72,20 @@ def _exec(self, command: str, *args: str) -> subprocess.CompletedProcess[str]:
8072
return subprocess.run(
8173
[f"{self.codeql_path}", command] + [arg for arg in args],
8274
capture_output=True,
83-
text=True,
75+
text=True
8476
)
8577

8678
def version(self) -> Version:
87-
cp = self._exec("version", "--format=json")
88-
if cp.returncode == 0:
89-
version_info = json.loads(cp.stdout)
90-
return Version(version_info["version"])
79+
if self._version != None:
80+
return self._version
9181
else:
92-
raise CodeQLException(f"Failed to run {cp.args} command!")
82+
cp = self._exec("version", "--format=json")
83+
if cp.returncode == 0:
84+
version_info = json.loads(cp.stdout)
85+
self._version = Version(version_info["version"])
86+
return self._version
87+
else:
88+
raise CodeQLException(f"Failed to run {cp.args} command!")
9389

9490
def unpacked_location(self) -> Path:
9591
cp = self._exec("version", "--format=json")
@@ -102,44 +98,36 @@ def unpacked_location(self) -> Path:
10298
def supports_qlx(self) -> bool:
10399
return self.version() >= Version("2.11.4")
104100

105-
def pack_ls(self, workspace: Path = Path.cwd()) -> List[ResolvedCodeQLPack]:
101+
def pack_ls(self, workspace: Path = Path.cwd()) -> List[CodeQLPack]:
106102
cp = self._exec("pack", "ls", "--format=json", str(workspace))
107103
if cp.returncode == 0:
108104
packs: Iterable[Path] = map(Path, json.loads(cp.stdout)["packs"].keys())
109105

110-
def load(qlpack: Path) -> ResolvedCodeQLPack:
111-
with qlpack.open("r") as qlpack_file:
112-
logger.debug(f"Resolving CodeQL pack at {qlpack}.")
113-
qlpack_spec = yaml.safe_load(qlpack_file)
114-
qlpack_spec["path"] = qlpack
115-
if not "library" in qlpack_spec or not qlpack_spec["library"]:
116-
qlpack_spec["kind"] = CodeQLPackKind.QUERY_PACK
117-
else:
118-
if (
119-
qlpack_spec["path"].parent
120-
/ qlpack_spec["name"].replace("-", "_")
121-
/ "Customizations.qll"
122-
).exists():
123-
qlpack_spec["kind"] = CodeQLPackKind.CUSTOMIZATION_PACK
124-
else:
125-
qlpack_spec["kind"] = CodeQLPackKind.LIBRARY_PACK
126-
resolved_pack = ResolvedCodeQLPack.from_dict(qlpack_spec)
106+
def load(qlpack_yml_path: Path) -> CodeQLPack:
107+
with qlpack_yml_path.open("r") as qlpack_yml_file:
108+
logger.debug(f"Loading CodeQL pack configuration at {qlpack_yml_path}.")
109+
qlpack_yml_as_dict: Dict[str, Any] = yaml.safe_load(qlpack_yml_file)
110+
qlpack_config = CodeQLPackConfig.from_dict(qlpack_yml_as_dict)
111+
qlpack = CodeQLPack(path=qlpack_yml_path, config=qlpack_config)
127112
logger.debug(
128-
f"Resolved {resolved_pack.name} with version {str(resolved_pack.version)} at {resolved_pack.path} with kind {resolved_pack.kind.name}"
113+
f"Loaded {qlpack.config.name} with version {str(qlpack.config.version)} at {qlpack.path}."
129114
)
130-
return resolved_pack
115+
return qlpack
131116

132-
logger.debug(f"Resolving CodeQL packs for workspace {workspace}")
117+
logger.debug(f"Listing CodeQL packs for workspace {workspace}")
133118
return list(map(load, packs))
134119
else:
135120
raise CodeQLException(f"Failed to run {cp.args} command! {cp.stderr}")
136121

137122
def pack_bundle(
138123
self,
139-
pack: ResolvedCodeQLPack,
124+
pack: CodeQLPack,
140125
output_path: Path,
141126
*additional_packs: Path,
142127
):
128+
if not pack.config.library:
129+
raise CodeQLException(f"Cannot bundle non-library pack {pack.config.name}!")
130+
143131
args = ["bundle", "--format=json", f"--pack-path={output_path}"]
144132
if len(additional_packs) > 0:
145133
args.append(f"--additional-packs={':'.join(map(str,additional_packs))}")
@@ -155,11 +143,14 @@ def pack_bundle(
155143

156144
def pack_create(
157145
self,
158-
pack: ResolvedCodeQLPack,
146+
pack: CodeQLPack,
159147
output_path: Path,
160148
*additional_packs: Path,
161149
):
162-
args = ["create", "--format=json", f"--output={output_path}", "--threads=0"]
150+
if pack.config.library:
151+
raise CodeQLException(f"Cannot bundle non-query pack {pack.config.name}!")
152+
153+
args = ["create", "--format=json", f"--output={output_path}", "--threads=0", "--no-default-compilation-cache"]
163154
if self.supports_qlx():
164155
args.append("--qlx")
165156
if len(additional_packs) > 0:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "codeql-bundle"
3-
version = "0.1.6"
3+
version = "0.1.7"
44
description = "Tool to create custom CodeQL bundles"
55
authors = ["Remco Vermeulen <[email protected]>"]
66
readme = "README.md"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
provide:
2+
- "**/qlpack.yml"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
library: false
3+
warnOnImplicitThis: false
4+
name: cycle/x
5+
version: 0.0.1
6+
dependencies:
7+
"cycle/y": "*"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
library: false
3+
warnOnImplicitThis: false
4+
name: cycle/y
5+
version: 0.0.1
6+
dependencies:
7+
"cycle/z": "*"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
library: false
3+
warnOnImplicitThis: false
4+
name: cycle/z
5+
version: 0.0.1
6+
dependencies:
7+
"cycle/x": "*"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
provide:
2+
- "**/qlpack.yml"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
lockVersion: 1.0.0
3+
dependencies:
4+
codeql/cpp-all:
5+
version: 0.7.3
6+
codeql/ssa:
7+
version: 0.0.18
8+
codeql/tutorial:
9+
version: 0.0.11
10+
codeql/util:
11+
version: 0.0.11
12+
compiled: false

0 commit comments

Comments
 (0)