mirror of
https://github.com/aphrody-labs/cef.git
synced 2026-10-13 21:07:07 +00:00
cefbuilds: Add installer CDN query file generation (see #4093)
Add cef_installer_builder.py to generate per-milestone and per-platform JSON manifests consumed by the CEF bootstrap installer. Extend cef_json_builder.py with abi_hash, signed archive tracking, and revocation list (revoked.json) support. Includes unit tests.
This commit is contained in:
1 parent
803fe341df
commit
7324f016a3
7 files changed
+1212
-60
No files matched your search
@@ -0,0 +1,142 @@
|
||||
# CEF Builds JSON Tools
|
||||
|
||||
Tools for generating and maintaining the JSON index files used by the
|
||||
[CEF Builds](https://cef-builds.spotifycdn.com/index.html) CDN and the CEF
|
||||
installer.
|
||||
|
||||
## Files
|
||||
|
||||
### Core libraries
|
||||
|
||||
- **`cef_json_builder.py`** — Builds and maintains the master `index.json` file
|
||||
that contains the full catalog of CEF binary distributions across all
|
||||
platforms, versions, and channels. Handles file metadata (name, size, sha1,
|
||||
last_modified), Chromium version mapping, and sandbox compatibility hashes.
|
||||
|
||||
- **`cef_installer_builder.py`** — Generates installer-optimized CDN query files
|
||||
from a populated `cef_json_builder` instance. Produces lightweight JSON files
|
||||
designed for efficient HTTP HEAD timestamp checks by the CEF installer.
|
||||
|
||||
### Data files
|
||||
|
||||
- **`revoked.json`** — Manually maintained list of revoked CEF versions. Revoked
|
||||
versions are excluded from all installer-generated JSON output. The installer
|
||||
also fetches this file from the CDN and refuses to install any listed version.
|
||||
|
||||
### Examples and tests
|
||||
|
||||
- **`cef_json_builder_example.py`** — Example script demonstrating
|
||||
`cef_json_builder` usage. Creates and updates a local `index.json` with fake
|
||||
file data.
|
||||
|
||||
- **`cef_json_builder_test.py`** — Unit tests for `cef_json_builder`.
|
||||
|
||||
- **`cef_installer_builder_test.py`** — Unit tests for `cef_installer_builder`.
|
||||
|
||||
## CDN file formats
|
||||
|
||||
### Master index
|
||||
|
||||
- **`index.json`** — Full data index of all CEF binary distributions. Generated
|
||||
by `cef_json_builder`. Keyed by platform, with version records sorted
|
||||
newest-first by file last_modified timestamp.
|
||||
|
||||
### Installer query files
|
||||
|
||||
Generated by `cef_installer_builder` from the master index:
|
||||
|
||||
- **`stable.txt`** — Current stable milestone number (e.g., `137`).
|
||||
|
||||
- **`{milestone}.json`** (e.g., `137.json`) — Newest build per platform for a
|
||||
given milestone. Used by the installer to check if an update is available.
|
||||
|
||||
- **`{milestone}_{platform}.json`** (e.g., `137_windows64.json`) — All builds
|
||||
for a milestone+platform combination, sorted newest-first.
|
||||
|
||||
- **`{abi_hash}_{platform}.json`** (e.g., `abc123def4567890_windows64.json`) —
|
||||
All builds compatible with a given ABI hash and platform across all milestones,
|
||||
sorted newest-first. Windows platforms only (tied to `chrome_elf.dll` /
|
||||
Chromium sandbox ABI).
|
||||
|
||||
## Concepts
|
||||
|
||||
### Platforms
|
||||
|
||||
Supported platforms: `linux32`, `linux64`, `linuxarm`, `linuxarm64`,
|
||||
`macosarm64`, `macosx64`, `windows32`, `windows64`, `windowsarm64`.
|
||||
|
||||
### Distribution types
|
||||
|
||||
Each version may have files of these types: `standard`, `minimal`, `client`,
|
||||
`tools`, `release_symbols`, `debug_symbols`, `signed`.
|
||||
|
||||
The `signed` type contains code-signed archives with catalog files, used by the
|
||||
installer for verified installation.
|
||||
|
||||
### Channels
|
||||
|
||||
Versions belong to either the `stable` or `beta` channel.
|
||||
|
||||
### Version formats
|
||||
|
||||
- **New-style**: `137.3.5+g62d140e+chromium-137.0.7204.6`
|
||||
- Short version: `137.3.5` (everything before the first `+`)
|
||||
- Milestone: `137` (major version number)
|
||||
- **Old-style**: `3.2704.1414.g185cd6c` (predates the installer format)
|
||||
|
||||
### ABI hash (`sandbox_compat`)
|
||||
|
||||
A 16-character hex string identifying the Windows sandbox ABI compatibility
|
||||
level. Derived from `cef_api_versions.json` on GitHub. Versions sharing the same
|
||||
ABI hash can use the same `chrome_elf.dll` and are interchangeable from the
|
||||
sandbox's perspective.
|
||||
|
||||
### Revocation
|
||||
|
||||
The `revoked.json` file lists versions that must not be installed. When passed
|
||||
to `cef_installer_builder`, revoked versions are automatically excluded from
|
||||
all generated output. The installer also fetches this file from the CDN at
|
||||
runtime and refuses to install any listed version.
|
||||
|
||||
Entries can revoke a single version or a range of versions:
|
||||
|
||||
```json
|
||||
{
|
||||
"revoked_versions": [
|
||||
{
|
||||
"version": "137.1.0",
|
||||
"reason": "CVE-2024-XXXXX: critical sandbox escape",
|
||||
"revoked_at": "2024-01-15T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version_min": "138.0.0",
|
||||
"version_max": "138.2.99",
|
||||
"reason": "CVE-2024-YYYYY: affects all 138.0–138.2 builds",
|
||||
"revoked_at": "2024-02-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Single version** (point revocation):
|
||||
- `version` — Short version string (e.g., `137.1.0`).
|
||||
|
||||
**Version range** (inclusive):
|
||||
- `version_min` — Inclusive lower bound (e.g., `138.0.0`).
|
||||
- `version_max` — Inclusive upper bound (e.g., `138.2.99`).
|
||||
|
||||
**Common fields:**
|
||||
- `reason` — Human-readable explanation (CVE ID, bug reference, etc.).
|
||||
- `revoked_at` — ISO 8601 timestamp of when the revocation was added.
|
||||
|
||||
A single-version entry using `version` is equivalent to setting both
|
||||
`version_min` and `version_max` to the same value. If both `version` and
|
||||
`version_min`/`version_max` are present, the range fields take precedence.
|
||||
|
||||
## Running tests
|
||||
|
||||
```bash
|
||||
cd cef/tools/cefbuilds
|
||||
python3 -m unittest cef_json_builder_test -v
|
||||
python3 -m unittest cef_installer_builder_test -v
|
||||
```
|
||||
@@ -0,0 +1,235 @@
|
||||
# Copyright (c) 2026 The Chromium Embedded Framework Authors. All rights
|
||||
# reserved. Use of this source code is governed by a BSD-style license that
|
||||
# can be found in the LICENSE file.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
import json
|
||||
|
||||
from cef_json_builder import cef_json_builder, cef_json_encoder
|
||||
|
||||
|
||||
class cef_installer_builder:
|
||||
"""Generates installer-specific CDN query files from cef_json_builder data."""
|
||||
|
||||
_WINDOWS_PLATFORMS = ('windows32', 'windows64', 'windowsarm64')
|
||||
|
||||
def __init__(self, builder, revoked_json=None):
|
||||
"""Create a new cef_installer_builder.
|
||||
|
||||
Args:
|
||||
builder: A populated cef_json_builder instance.
|
||||
revoked_json: Optional parsed revoked.json dict (result of json.loads()).
|
||||
When provided, revoked versions are excluded from all generated output.
|
||||
"""
|
||||
self._builder = builder
|
||||
# Trigger version sorting (normally only done on __repr__).
|
||||
self._builder._sort_versions()
|
||||
self._revoked_points = set()
|
||||
self._revoked_ranges = []
|
||||
if revoked_json is not None:
|
||||
for entry in revoked_json.get('revoked_versions', []):
|
||||
if 'version_min' in entry and 'version_max' in entry:
|
||||
self._revoked_ranges.append(
|
||||
(entry['version_min'], entry['version_max']))
|
||||
elif 'version' in entry:
|
||||
self._revoked_points.add(entry['version'])
|
||||
|
||||
@staticmethod
|
||||
def _parse_version_tuple(version_str):
|
||||
"""Parse '137.3.5' into (137, 3, 5) for comparison.
|
||||
|
||||
Pads with zeros so '137' becomes (137, 0, 0) and '137.4' becomes
|
||||
(137, 4, 0).
|
||||
"""
|
||||
parts = [int(x) for x in version_str.split('.')]
|
||||
while len(parts) < 3:
|
||||
parts.append(0)
|
||||
return tuple(parts)
|
||||
|
||||
def _is_revoked(self, short_version):
|
||||
"""Returns True if the short version is revoked (point or range)."""
|
||||
if short_version in self._revoked_points:
|
||||
return True
|
||||
v = self._parse_version_tuple(short_version)
|
||||
for vmin, vmax in self._revoked_ranges:
|
||||
if (self._parse_version_tuple(vmin) <= v <=
|
||||
self._parse_version_tuple(vmax)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_installer_entry(self, version_obj):
|
||||
"""Build one installer entry from a version record.
|
||||
|
||||
Returns None if the version has no signed-type file or is revoked.
|
||||
"""
|
||||
cef_version = version_obj['cef_version']
|
||||
short_version = cef_json_builder.get_short_version(cef_version)
|
||||
|
||||
if self._is_revoked(short_version):
|
||||
return None
|
||||
|
||||
# Find the signed file.
|
||||
signed_file = None
|
||||
for f in version_obj['files']:
|
||||
if f['type'] == 'signed':
|
||||
signed_file = f
|
||||
break
|
||||
|
||||
if signed_file is None:
|
||||
return None
|
||||
|
||||
entry = {
|
||||
'version': short_version,
|
||||
'file': signed_file['name'],
|
||||
'sha1': signed_file['sha1'],
|
||||
'last_modified': signed_file['last_modified'],
|
||||
}
|
||||
|
||||
sandbox_compat = version_obj.get('sandbox_compat')
|
||||
if sandbox_compat is not None:
|
||||
entry['abi_hash'] = sandbox_compat
|
||||
|
||||
return entry
|
||||
|
||||
@staticmethod
|
||||
def _to_json(result, prettyprint):
|
||||
"""Serialize result to a JSON string with sorted keys."""
|
||||
if prettyprint:
|
||||
return json.dumps(result,
|
||||
cls=cef_json_encoder,
|
||||
sort_keys=True,
|
||||
indent=2,
|
||||
separators=(',', ': '))
|
||||
return json.dumps(result, cls=cef_json_encoder, sort_keys=True)
|
||||
|
||||
def get_stable_milestone(self, channel='stable'):
|
||||
"""Returns the highest milestone integer in the given channel.
|
||||
|
||||
Returns None if no versions exist.
|
||||
"""
|
||||
highest = None
|
||||
for platform in self._builder.get_platforms():
|
||||
for version_obj in self._builder.get_versions(platform):
|
||||
if version_obj.get('channel', 'stable') != channel:
|
||||
continue
|
||||
milestone = cef_json_builder.get_milestone(version_obj['cef_version'])
|
||||
if highest is None or milestone > highest:
|
||||
highest = milestone
|
||||
return highest
|
||||
|
||||
def generate_stable_txt(self, channel='stable'):
|
||||
"""Returns string content for stable.txt (just the milestone number).
|
||||
|
||||
Returns None if no versions exist.
|
||||
"""
|
||||
milestone = self.get_stable_milestone(channel)
|
||||
if milestone is None:
|
||||
return None
|
||||
return str(milestone)
|
||||
|
||||
def generate_milestone_json(self,
|
||||
milestone,
|
||||
channel='stable',
|
||||
prettyprint=False):
|
||||
"""Returns JSON for {milestone}.json — newest version per platform.
|
||||
|
||||
Returns '{}' when no platforms qualify.
|
||||
"""
|
||||
result = {}
|
||||
for platform in self._builder.get_platforms():
|
||||
for version_obj in self._builder.get_versions(platform):
|
||||
if version_obj.get('channel', 'stable') != channel:
|
||||
continue
|
||||
if cef_json_builder.get_milestone(
|
||||
version_obj['cef_version']) != milestone:
|
||||
continue
|
||||
entry = self._get_installer_entry(version_obj)
|
||||
if entry is not None:
|
||||
# First matching version is newest (sorted by last_modified).
|
||||
result[platform] = entry
|
||||
break
|
||||
|
||||
return self._to_json(result, prettyprint)
|
||||
|
||||
def generate_milestone_platform_json(self,
|
||||
milestone,
|
||||
platform,
|
||||
channel='stable',
|
||||
prettyprint=False):
|
||||
"""Returns JSON for {milestone}_{platform}.json — all versions for a
|
||||
milestone+platform, sorted newest-first.
|
||||
|
||||
Returns '[]' for unknown milestone/platform combinations.
|
||||
"""
|
||||
result = []
|
||||
for version_obj in self._builder.get_versions(platform):
|
||||
if version_obj.get('channel', 'stable') != channel:
|
||||
continue
|
||||
if cef_json_builder.get_milestone(
|
||||
version_obj['cef_version']) != milestone:
|
||||
continue
|
||||
entry = self._get_installer_entry(version_obj)
|
||||
if entry is not None:
|
||||
result.append(entry)
|
||||
|
||||
return self._to_json(result, prettyprint)
|
||||
|
||||
def generate_abi_hash_platform_json(self,
|
||||
abi_hash,
|
||||
platform,
|
||||
channel='stable',
|
||||
prettyprint=False):
|
||||
"""Returns JSON for {abi_hash}_{platform}.json — all builds matching
|
||||
abi_hash for a platform across all milestones, sorted newest-first.
|
||||
|
||||
Only applicable to Windows platforms. Returns '[]' for non-Windows.
|
||||
"""
|
||||
if platform not in self._WINDOWS_PLATFORMS:
|
||||
return '[]'
|
||||
|
||||
result = []
|
||||
for version_obj in self._builder.get_versions(platform):
|
||||
if version_obj.get('channel', 'stable') != channel:
|
||||
continue
|
||||
if version_obj.get('sandbox_compat') != abi_hash:
|
||||
continue
|
||||
entry = self._get_installer_entry(version_obj)
|
||||
if entry is not None:
|
||||
result.append(entry)
|
||||
|
||||
return self._to_json(result, prettyprint)
|
||||
|
||||
def get_abi_hashes(self, platform=None, channel='stable'):
|
||||
"""Returns the set of distinct sandbox_compat values.
|
||||
|
||||
Args:
|
||||
platform: Optional platform filter. Non-Windows platforms return empty.
|
||||
channel: Channel filter (default 'stable').
|
||||
"""
|
||||
if platform is not None and platform not in self._WINDOWS_PLATFORMS:
|
||||
return set()
|
||||
|
||||
platforms = [platform] if platform else self._builder.get_platforms()
|
||||
hashes = set()
|
||||
for p in platforms:
|
||||
if p not in self._WINDOWS_PLATFORMS:
|
||||
continue
|
||||
for version_obj in self._builder.get_versions(p):
|
||||
if version_obj.get('channel', 'stable') != channel:
|
||||
continue
|
||||
sc = version_obj.get('sandbox_compat')
|
||||
if sc is not None:
|
||||
hashes.add(sc)
|
||||
return hashes
|
||||
|
||||
def get_milestones(self, channel='stable'):
|
||||
"""Returns sorted list of all milestone integers in the given channel."""
|
||||
milestones = set()
|
||||
for platform in self._builder.get_platforms():
|
||||
for version_obj in self._builder.get_versions(platform):
|
||||
if version_obj.get('channel', 'stable') != channel:
|
||||
continue
|
||||
milestones.add(
|
||||
cef_json_builder.get_milestone(version_obj['cef_version']))
|
||||
return sorted(milestones)
|
||||
@@ -0,0 +1,618 @@
|
||||
# Copyright (c) 2026 The Chromium Embedded Framework Authors. All rights
|
||||
# reserved. Use of this source code is governed by a BSD-style license that
|
||||
# can be found in the LICENSE file.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from cef_json_builder import cef_json_builder
|
||||
from cef_installer_builder import cef_installer_builder
|
||||
import datetime
|
||||
import json
|
||||
import unittest
|
||||
|
||||
|
||||
class TestCefInstallerBuilder(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# Allow sandbox_compat queries for all milestones in tests.
|
||||
self._orig_min = cef_json_builder.MIN_SANDBOX_COMPAT_MILESTONE
|
||||
cef_json_builder.MIN_SANDBOX_COMPAT_MILESTONE = 0
|
||||
|
||||
def tearDown(self):
|
||||
cef_json_builder.MIN_SANDBOX_COMPAT_MILESTONE = self._orig_min
|
||||
|
||||
# Helper to create a builder pre-populated with version data.
|
||||
# Avoids remote queries by setting chromium_version and sandbox_compat.
|
||||
def _create_builder(self):
|
||||
return cef_json_builder(silent=True)
|
||||
|
||||
def _add_version(self,
|
||||
builder,
|
||||
platform,
|
||||
cef_version,
|
||||
chromium_version,
|
||||
types=None,
|
||||
channel='stable',
|
||||
sandbox_compat=None,
|
||||
last_modified=None):
|
||||
"""Add a version with specified file types to the builder.
|
||||
|
||||
Args:
|
||||
types: List of distribution types to add. Defaults to ['signed'].
|
||||
last_modified: datetime or string. Defaults to a fixed time.
|
||||
"""
|
||||
if types is None:
|
||||
types = ['signed']
|
||||
if last_modified is None:
|
||||
last_modified = '2026-02-10T12:00:00.000Z'
|
||||
|
||||
# Pre-populate to avoid queries.
|
||||
builder.set_chromium_version(cef_version, chromium_version)
|
||||
if sandbox_compat is not None:
|
||||
builder.set_sandbox_compat(cef_version, sandbox_compat)
|
||||
|
||||
for t in types:
|
||||
name = cef_json_builder.get_file_name(cef_version, platform, t,
|
||||
channel) + '.tar.bz2'
|
||||
builder.add_file(name, 12345, last_modified, 'a' * 40)
|
||||
|
||||
# --- test_get_stable_milestone ---
|
||||
|
||||
def test_get_stable_milestone(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder, 'linux64',
|
||||
'136.0.3+g1111111+chromium-136.0.6778.0', '136.0.6778.0')
|
||||
self._add_version(builder, 'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6', '137.0.7204.6')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
self.assertEqual(137, ib.get_stable_milestone())
|
||||
|
||||
def test_get_stable_milestone_empty(self):
|
||||
builder = self._create_builder()
|
||||
ib = cef_installer_builder(builder)
|
||||
self.assertIsNone(ib.get_stable_milestone())
|
||||
|
||||
# --- test_generate_stable_txt ---
|
||||
|
||||
def test_generate_stable_txt(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder, 'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6', '137.0.7204.6')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
self.assertEqual('137', ib.generate_stable_txt())
|
||||
|
||||
def test_generate_stable_txt_empty(self):
|
||||
builder = self._create_builder()
|
||||
ib = cef_installer_builder(builder)
|
||||
self.assertIsNone(ib.generate_stable_txt())
|
||||
|
||||
# --- test_generate_milestone_json ---
|
||||
|
||||
def test_generate_milestone_json_basic(self):
|
||||
builder = self._create_builder()
|
||||
# Add versions on two platforms (use different cef_versions to avoid
|
||||
# sandbox_compat cache sharing across platforms).
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
sandbox_compat='abc123def4567890')
|
||||
self._add_version(builder, 'linux64',
|
||||
'137.3.4+g1111111+chromium-137.0.7204.5', '137.0.7204.5')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
result = json.loads(ib.generate_milestone_json(137))
|
||||
|
||||
# Both platforms present.
|
||||
self.assertIn('windows64', result)
|
||||
self.assertIn('linux64', result)
|
||||
|
||||
# Windows has abi_hash.
|
||||
self.assertEqual('137.3.5', result['windows64']['version'])
|
||||
self.assertEqual('abc123def4567890', result['windows64']['abi_hash'])
|
||||
self.assertIn('signed', result['windows64']['file'])
|
||||
|
||||
# Linux has no abi_hash.
|
||||
self.assertEqual('137.3.4', result['linux64']['version'])
|
||||
self.assertNotIn('abi_hash', result['linux64'])
|
||||
|
||||
def test_generate_milestone_json_newest_version(self):
|
||||
builder = self._create_builder()
|
||||
# Older version added first with earlier timestamp.
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.1.1+g1111111+chromium-137.0.7204.0',
|
||||
'137.0.7204.0',
|
||||
last_modified='2026-02-08T12:00:00.000Z')
|
||||
# Newer version added second with later timestamp.
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
last_modified='2026-02-10T12:00:00.000Z')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
result = json.loads(ib.generate_milestone_json(137))
|
||||
|
||||
# Should pick the newest version.
|
||||
self.assertEqual('137.3.5', result['linux64']['version'])
|
||||
|
||||
def test_generate_milestone_json_no_abi_hash(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder, 'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6', '137.0.7204.6')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
result = json.loads(ib.generate_milestone_json(137))
|
||||
|
||||
self.assertNotIn('abi_hash', result['linux64'])
|
||||
|
||||
def test_generate_milestone_json_omits_platforms_without_signed(self):
|
||||
builder = self._create_builder()
|
||||
# Only add 'minimal' type, no 'signed'.
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
types=['minimal'])
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
result = json.loads(ib.generate_milestone_json(137))
|
||||
|
||||
self.assertNotIn('linux64', result)
|
||||
|
||||
def test_generate_milestone_json_empty(self):
|
||||
builder = self._create_builder()
|
||||
ib = cef_installer_builder(builder)
|
||||
self.assertEqual('{}', ib.generate_milestone_json(999))
|
||||
|
||||
# --- test_generate_milestone_platform_json ---
|
||||
|
||||
def test_generate_milestone_platform_json_ordering(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.1.1+g1111111+chromium-137.0.7204.0',
|
||||
'137.0.7204.0',
|
||||
last_modified='2026-02-08T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
last_modified='2026-02-10T12:00:00.000Z')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
result = json.loads(ib.generate_milestone_platform_json(137, 'linux64'))
|
||||
|
||||
self.assertEqual(2, len(result))
|
||||
# Newest first.
|
||||
self.assertEqual('137.3.5', result[0]['version'])
|
||||
self.assertEqual('137.1.1', result[1]['version'])
|
||||
|
||||
def test_generate_milestone_platform_json_filters_channel(self):
|
||||
builder = self._create_builder()
|
||||
# Stable version.
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
channel='stable')
|
||||
# Beta version.
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.4.0+g2222222+chromium-137.0.7204.8',
|
||||
'137.0.7204.8',
|
||||
channel='beta')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
|
||||
# Stable query should only return stable.
|
||||
result_stable = json.loads(
|
||||
ib.generate_milestone_platform_json(137, 'linux64', channel='stable'))
|
||||
self.assertEqual(1, len(result_stable))
|
||||
self.assertEqual('137.3.5', result_stable[0]['version'])
|
||||
|
||||
# Beta query should only return beta.
|
||||
result_beta = json.loads(
|
||||
ib.generate_milestone_platform_json(137, 'linux64', channel='beta'))
|
||||
self.assertEqual(1, len(result_beta))
|
||||
self.assertEqual('137.4.0', result_beta[0]['version'])
|
||||
|
||||
def test_generate_milestone_platform_json_empty(self):
|
||||
builder = self._create_builder()
|
||||
ib = cef_installer_builder(builder)
|
||||
self.assertEqual('[]', ib.generate_milestone_platform_json(999, 'linux64'))
|
||||
|
||||
# --- test_generate_abi_hash_platform_json ---
|
||||
|
||||
def test_generate_abi_hash_platform_json_basic(self):
|
||||
builder = self._create_builder()
|
||||
# Same abi_hash across two milestones.
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'136.0.3+g1111111+chromium-136.0.6778.0',
|
||||
'136.0.6778.0',
|
||||
sandbox_compat='abc123def4567890',
|
||||
last_modified='2026-02-05T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
sandbox_compat='abc123def4567890',
|
||||
last_modified='2026-02-10T12:00:00.000Z')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
result = json.loads(
|
||||
ib.generate_abi_hash_platform_json('abc123def4567890', 'windows64'))
|
||||
|
||||
# Both milestones present.
|
||||
self.assertEqual(2, len(result))
|
||||
|
||||
def test_generate_abi_hash_platform_json_ordering(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'136.0.3+g1111111+chromium-136.0.6778.0',
|
||||
'136.0.6778.0',
|
||||
sandbox_compat='abc123def4567890',
|
||||
last_modified='2026-02-05T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
sandbox_compat='abc123def4567890',
|
||||
last_modified='2026-02-10T12:00:00.000Z')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
result = json.loads(
|
||||
ib.generate_abi_hash_platform_json('abc123def4567890', 'windows64'))
|
||||
|
||||
# Newest first.
|
||||
self.assertEqual('137.3.5', result[0]['version'])
|
||||
self.assertEqual('136.0.3', result[1]['version'])
|
||||
|
||||
def test_generate_abi_hash_platform_json_excludes_other_hashes(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
sandbox_compat='abc123def4567890',
|
||||
last_modified='2026-02-10T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'138.0.1+g3333333+chromium-138.0.7204.0',
|
||||
'138.0.7204.0',
|
||||
sandbox_compat='def456abc7890123',
|
||||
last_modified='2026-02-12T12:00:00.000Z')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
result = json.loads(
|
||||
ib.generate_abi_hash_platform_json('abc123def4567890', 'windows64'))
|
||||
|
||||
self.assertEqual(1, len(result))
|
||||
self.assertEqual('137.3.5', result[0]['version'])
|
||||
|
||||
def test_generate_abi_hash_platform_json_non_windows(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder, 'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6', '137.0.7204.6')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
self.assertEqual(
|
||||
'[]', ib.generate_abi_hash_platform_json('abc123def4567890', 'linux64'))
|
||||
self.assertEqual(
|
||||
'[]', ib.generate_abi_hash_platform_json('abc123def4567890',
|
||||
'macosx64'))
|
||||
|
||||
# --- test_get_abi_hashes ---
|
||||
|
||||
def test_get_abi_hashes(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
sandbox_compat='abc123def4567890')
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'138.0.1+g3333333+chromium-138.0.7204.0',
|
||||
'138.0.7204.0',
|
||||
sandbox_compat='def456abc7890123')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
hashes = ib.get_abi_hashes(platform='windows64')
|
||||
self.assertEqual({'abc123def4567890', 'def456abc7890123'}, hashes)
|
||||
|
||||
def test_get_abi_hashes_non_windows(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder, 'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6', '137.0.7204.6')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
self.assertEqual(set(), ib.get_abi_hashes(platform='linux64'))
|
||||
|
||||
# --- test_get_milestones ---
|
||||
|
||||
def test_get_milestones(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder, 'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6', '137.0.7204.6')
|
||||
self._add_version(builder, 'linux64',
|
||||
'136.0.3+g1111111+chromium-136.0.6778.0', '136.0.6778.0')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
self.assertEqual([136, 137], ib.get_milestones())
|
||||
|
||||
# --- test_skips_versions_without_signed_file ---
|
||||
|
||||
def test_skips_versions_without_signed_file(self):
|
||||
builder = self._create_builder()
|
||||
# Only add 'minimal' and 'client' types, no 'signed'.
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
types=['minimal', 'client'])
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
result = json.loads(ib.generate_milestone_platform_json(137, 'linux64'))
|
||||
self.assertEqual(0, len(result))
|
||||
|
||||
# --- test_revoked_versions_excluded ---
|
||||
|
||||
def test_revoked_versions_excluded(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.1.0+g1111111+chromium-137.0.7204.0',
|
||||
'137.0.7204.0',
|
||||
last_modified='2026-02-08T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
last_modified='2026-02-10T12:00:00.000Z')
|
||||
|
||||
revoked = {
|
||||
'revoked_versions': [{
|
||||
'version': '137.1.0',
|
||||
'reason': 'CVE-2024-XXXXX',
|
||||
'revoked_at': '2024-01-15T00:00:00Z'
|
||||
}]
|
||||
}
|
||||
ib = cef_installer_builder(builder, revoked_json=revoked)
|
||||
|
||||
# milestone_platform_json should exclude the revoked version.
|
||||
result = json.loads(ib.generate_milestone_platform_json(137, 'linux64'))
|
||||
self.assertEqual(1, len(result))
|
||||
self.assertEqual('137.3.5', result[0]['version'])
|
||||
|
||||
# milestone_json should pick non-revoked newest.
|
||||
result_milestone = json.loads(ib.generate_milestone_json(137))
|
||||
self.assertEqual('137.3.5', result_milestone['linux64']['version'])
|
||||
|
||||
def test_revoked_versions_excluded_from_abi_hash(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'137.1.0+g1111111+chromium-137.0.7204.0',
|
||||
'137.0.7204.0',
|
||||
sandbox_compat='abc123def4567890',
|
||||
last_modified='2026-02-08T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
sandbox_compat='abc123def4567890',
|
||||
last_modified='2026-02-10T12:00:00.000Z')
|
||||
|
||||
revoked = {
|
||||
'revoked_versions': [{
|
||||
'version': '137.1.0',
|
||||
'reason': 'test',
|
||||
'revoked_at': '2024-01-15T00:00:00Z'
|
||||
}]
|
||||
}
|
||||
ib = cef_installer_builder(builder, revoked_json=revoked)
|
||||
result = json.loads(
|
||||
ib.generate_abi_hash_platform_json('abc123def4567890', 'windows64'))
|
||||
|
||||
self.assertEqual(1, len(result))
|
||||
self.assertEqual('137.3.5', result[0]['version'])
|
||||
|
||||
def test_revoked_range_excludes_matching_versions(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.1.0+g1111111+chromium-137.0.7204.0',
|
||||
'137.0.7204.0',
|
||||
last_modified='2026-02-08T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.2.0+g2222222+chromium-137.0.7204.2',
|
||||
'137.0.7204.2',
|
||||
last_modified='2026-02-09T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
last_modified='2026-02-10T12:00:00.000Z')
|
||||
|
||||
revoked = {
|
||||
'revoked_versions': [{
|
||||
'version_min': '137.1.0',
|
||||
'version_max': '137.2.99',
|
||||
'reason': 'CVE-2024-YYYYY',
|
||||
'revoked_at': '2024-02-01T00:00:00Z'
|
||||
}]
|
||||
}
|
||||
ib = cef_installer_builder(builder, revoked_json=revoked)
|
||||
|
||||
result = json.loads(ib.generate_milestone_platform_json(137, 'linux64'))
|
||||
self.assertEqual(1, len(result))
|
||||
self.assertEqual('137.3.5', result[0]['version'])
|
||||
|
||||
def test_revoked_range_and_point_combined(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.1.0+g1111111+chromium-137.0.7204.0',
|
||||
'137.0.7204.0',
|
||||
last_modified='2026-02-08T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.2.0+g2222222+chromium-137.0.7204.2',
|
||||
'137.0.7204.2',
|
||||
last_modified='2026-02-09T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
last_modified='2026-02-10T12:00:00.000Z')
|
||||
|
||||
revoked = {
|
||||
'revoked_versions': [
|
||||
{
|
||||
'version': '137.3.5',
|
||||
'reason': 'point revocation',
|
||||
'revoked_at': '2024-01-15T00:00:00Z'
|
||||
},
|
||||
{
|
||||
'version_min': '137.1.0',
|
||||
'version_max': '137.1.99',
|
||||
'reason': 'range revocation',
|
||||
'revoked_at': '2024-02-01T00:00:00Z'
|
||||
},
|
||||
]
|
||||
}
|
||||
ib = cef_installer_builder(builder, revoked_json=revoked)
|
||||
|
||||
# Only 137.2.0 survives.
|
||||
result = json.loads(ib.generate_milestone_platform_json(137, 'linux64'))
|
||||
self.assertEqual(1, len(result))
|
||||
self.assertEqual('137.2.0', result[0]['version'])
|
||||
|
||||
def test_revoked_range_partial_version_specs(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'137.1.0+g1111111+chromium-137.0.7204.0',
|
||||
'137.0.7204.0',
|
||||
last_modified='2026-02-08T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'138.0.0+g2222222+chromium-138.0.7300.0',
|
||||
'138.0.7300.0',
|
||||
last_modified='2026-02-09T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'138.3.0+g3333333+chromium-138.0.7300.3',
|
||||
'138.0.7300.3',
|
||||
last_modified='2026-02-10T12:00:00.000Z')
|
||||
self._add_version(builder,
|
||||
'linux64',
|
||||
'139.0.0+g4444444+chromium-139.0.7400.0',
|
||||
'139.0.7400.0',
|
||||
last_modified='2026-02-11T12:00:00.000Z')
|
||||
|
||||
# "138" means 138.0.0, "138.2" means 138.2.0 — revokes 138.0.0 but
|
||||
# not 138.3.0.
|
||||
revoked = {
|
||||
'revoked_versions': [{
|
||||
'version_min': '138',
|
||||
'version_max': '138.2',
|
||||
'reason': 'partial version spec test',
|
||||
'revoked_at': '2024-02-01T00:00:00Z'
|
||||
}]
|
||||
}
|
||||
ib = cef_installer_builder(builder, revoked_json=revoked)
|
||||
|
||||
result = json.loads(ib.generate_milestone_platform_json(138, 'linux64'))
|
||||
self.assertEqual(1, len(result))
|
||||
self.assertEqual('138.3.0', result[0]['version'])
|
||||
|
||||
# --- Round-trip tests ---
|
||||
|
||||
def test_round_trip_milestone_json(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
sandbox_compat='abc123def4567890')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
output = ib.generate_milestone_json(137)
|
||||
parsed = json.loads(output)
|
||||
self.assertIsInstance(parsed, dict)
|
||||
self.assertIn('windows64', parsed)
|
||||
|
||||
def test_round_trip_milestone_platform_json(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder, 'linux64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6', '137.0.7204.6')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
output = ib.generate_milestone_platform_json(137, 'linux64')
|
||||
parsed = json.loads(output)
|
||||
self.assertIsInstance(parsed, list)
|
||||
self.assertEqual(1, len(parsed))
|
||||
|
||||
def test_round_trip_abi_hash_platform_json(self):
|
||||
builder = self._create_builder()
|
||||
self._add_version(builder,
|
||||
'windows64',
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6',
|
||||
'137.0.7204.6',
|
||||
sandbox_compat='abc123def4567890')
|
||||
|
||||
ib = cef_installer_builder(builder)
|
||||
output = ib.generate_abi_hash_platform_json('abc123def4567890', 'windows64')
|
||||
parsed = json.loads(output)
|
||||
self.assertIsInstance(parsed, list)
|
||||
self.assertEqual(1, len(parsed))
|
||||
|
||||
|
||||
class TestVersionTupleComparison(unittest.TestCase):
|
||||
"""Verify _parse_version_tuple comparison matches base::Version semantics.
|
||||
|
||||
base::Version treats missing trailing components as zero, so
|
||||
Version("137") == Version("137.0") == Version("137.0.0"). The Python
|
||||
tuple comparison must agree.
|
||||
"""
|
||||
|
||||
_parse = staticmethod(cef_installer_builder._parse_version_tuple)
|
||||
|
||||
def test_single_component_equals_padded(self):
|
||||
self.assertEqual(self._parse('137'), self._parse('137.0.0'))
|
||||
|
||||
def test_two_components_equals_padded(self):
|
||||
self.assertEqual(self._parse('137.4'), self._parse('137.4.0'))
|
||||
|
||||
def test_three_components_unchanged(self):
|
||||
self.assertEqual(self._parse('137.3.5'), (137, 3, 5))
|
||||
|
||||
def test_less_than_across_component_counts(self):
|
||||
self.assertLess(self._parse('137'), self._parse('137.0.1'))
|
||||
self.assertLess(self._parse('137.3'), self._parse('137.3.1'))
|
||||
self.assertLess(self._parse('136'), self._parse('137'))
|
||||
|
||||
def test_greater_than_across_component_counts(self):
|
||||
self.assertGreater(self._parse('138'), self._parse('137.99.99'))
|
||||
self.assertGreater(self._parse('137.4'), self._parse('137.3.99'))
|
||||
|
||||
def test_range_check_with_partial_bounds(self):
|
||||
"""Simulate the range check: version_min <= version <= version_max."""
|
||||
v = self._parse('137.3.5')
|
||||
self.assertTrue(self._parse('137') <= v <= self._parse('137.99'))
|
||||
self.assertTrue(self._parse('137.3') <= v <= self._parse('137.3.5'))
|
||||
self.assertFalse(self._parse('137.3') <= v <= self._parse('137.3'))
|
||||
self.assertFalse(self._parse('137.4') <= v <= self._parse('138'))
|
||||
self.assertFalse(self._parse('137') <= v <= self._parse('137.3.4'))
|
||||
|
||||
|
||||
# Program entry point.
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -90,6 +90,10 @@ _cef_version_dev_regex = _cef_number_regex + r'\-\w+\.[0-9]{1,7}\+' + _cef_hash_
|
||||
class cef_json_builder:
|
||||
""" Class used to build the cefbuilds JSON file. """
|
||||
|
||||
# Minimum milestone that supports sandbox_compat (ABI hash) queries.
|
||||
# Can be overridden for testing.
|
||||
MIN_SANDBOX_COMPAT_MILESTONE = 146
|
||||
|
||||
def __init__(self, prettyprint=False, silent=True):
|
||||
""" Create a new cef_json_builder object. """
|
||||
self._prettyprint = prettyprint
|
||||
@@ -107,7 +111,7 @@ class cef_json_builder:
|
||||
def get_distrib_types():
|
||||
""" Returns the list of supported distribution types. """
|
||||
return ('standard', 'minimal', 'client', 'tools', 'release_symbols',
|
||||
'debug_symbols')
|
||||
'debug_symbols', 'signed')
|
||||
|
||||
@staticmethod
|
||||
def is_valid_version(version):
|
||||
@@ -134,6 +138,16 @@ class cef_json_builder:
|
||||
# 16-character hex string
|
||||
return bool(re.compile('^[0-9a-f]{16}$').match(sandbox_compat))
|
||||
|
||||
@staticmethod
|
||||
def get_short_version(cef_version):
|
||||
"""Extract short version (e.g., '137.3.5') from full CEF version."""
|
||||
return cef_version.split('+')[0]
|
||||
|
||||
@staticmethod
|
||||
def get_milestone(cef_version):
|
||||
"""Extract milestone (major version number) from CEF version."""
|
||||
return int(cef_version.split('.')[0])
|
||||
|
||||
@staticmethod
|
||||
def cef_version_to_api_version(cef_version):
|
||||
""" Convert CEF version to API version number.
|
||||
@@ -190,12 +204,11 @@ class cef_json_builder:
|
||||
# Return a string representation of this object.
|
||||
self._sort_versions()
|
||||
if self._prettyprint:
|
||||
return json.dumps(
|
||||
self._data,
|
||||
cls=cef_json_encoder,
|
||||
sort_keys=True,
|
||||
indent=2,
|
||||
separators=(',', ': '))
|
||||
return json.dumps(self._data,
|
||||
cls=cef_json_encoder,
|
||||
sort_keys=True,
|
||||
indent=2,
|
||||
separators=(',', ': '))
|
||||
else:
|
||||
return json.dumps(self._data, cls=cef_json_encoder, sort_keys=True)
|
||||
|
||||
@@ -312,6 +325,10 @@ class cef_json_builder:
|
||||
# Old-style version numbers don't have API versioning.
|
||||
return None
|
||||
|
||||
# sandbox_compat (ABI hash) only exists for recent milestones.
|
||||
if self.get_milestone(cef_version) < self.MIN_SANDBOX_COMPAT_MILESTONE:
|
||||
return None
|
||||
|
||||
json_data = self._query_api_versions_json(cef_version)
|
||||
if json_data is None or 'hashes' not in json_data:
|
||||
return None
|
||||
@@ -494,6 +511,11 @@ class cef_json_builder:
|
||||
type = 'tools'
|
||||
del name_parts[-1]
|
||||
|
||||
# Might be '<version>_<platform>_signed'.
|
||||
if name_parts[-1] == 'signed':
|
||||
type = 'signed'
|
||||
del name_parts[-1]
|
||||
|
||||
# Might be '<version>_<platform>_beta'.
|
||||
if name_parts[-1] == 'beta':
|
||||
del name_parts[-1]
|
||||
|
||||
@@ -11,12 +11,17 @@
|
||||
# Add files for all platforms at the specified version:
|
||||
# > python cef_json_builder_example.py add all 3.2704.1416.g185cd6c 51.0.2704.47
|
||||
#
|
||||
# Add files and also generate installer query files:
|
||||
# > python cef_json_builder_example.py add all 137.3.5+g62d140e+chromium-137.0.7204.6 137.0.7204.6 --installer
|
||||
#
|
||||
# See cef_json_builder.get_platforms() for the list of supported platforms.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from cef_json_builder import cef_json_builder
|
||||
from cef_installer_builder import cef_installer_builder
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
@@ -25,8 +30,8 @@ import sys
|
||||
|
||||
# Create a fake sha1 checksum value.
|
||||
def make_fake_sha1():
|
||||
return ''.join(random.SystemRandom().choice('abcdef' + string.digits)
|
||||
for _ in range(40))
|
||||
return ''.join(
|
||||
random.SystemRandom().choice('abcdef' + string.digits) for _ in range(40))
|
||||
|
||||
|
||||
# Create a fake file size value.
|
||||
@@ -63,6 +68,9 @@ def create_fake_files(platform, version):
|
||||
files.append(make_fake_file_info(platform, version, 'debug_symbols'))
|
||||
files.append(make_fake_file_info(platform, version, 'release_symbols'))
|
||||
|
||||
# All platforms create signed distributions (for installer use).
|
||||
files.append(make_fake_file_info(platform, version, 'signed'))
|
||||
|
||||
return files
|
||||
|
||||
|
||||
@@ -71,10 +79,13 @@ if __name__ == '__main__':
|
||||
# Verify command-line arguments.
|
||||
if len(sys.argv) < 5 or sys.argv[1] != 'add':
|
||||
sys.stderr.write(
|
||||
'Usage: %s add <platform> <cef_version> <chromium_version>\n' %
|
||||
sys.argv[0])
|
||||
'Usage: %s add <platform> <cef_version> <chromium_version> [--installer]\n'
|
||||
% sys.argv[0])
|
||||
sys.exit()
|
||||
|
||||
# Check for --installer flag.
|
||||
generate_installer = '--installer' in sys.argv
|
||||
|
||||
# Requested platform.
|
||||
if sys.argv[2] == 'all':
|
||||
platforms = cef_json_builder.get_platforms()
|
||||
@@ -148,3 +159,69 @@ if __name__ == '__main__':
|
||||
# A real implementation would now upload the changed files.
|
||||
for file in changed_files:
|
||||
print('--> Upload file %s' % file['name'])
|
||||
|
||||
# Optionally generate installer query files.
|
||||
if generate_installer:
|
||||
# Load revoked.json if it exists.
|
||||
revoked_file = os.path.join(current_dir, 'revoked.json')
|
||||
revoked_json = None
|
||||
if os.path.exists(revoked_file):
|
||||
print('--> Reading revoked.json')
|
||||
with open(revoked_file, 'r') as f:
|
||||
revoked_json = json.loads(f.read())
|
||||
|
||||
installer = cef_installer_builder(builder, revoked_json=revoked_json)
|
||||
|
||||
# Generate stable.txt.
|
||||
for channel in ('stable', 'beta'):
|
||||
stable_txt = installer.generate_stable_txt(channel=channel)
|
||||
if stable_txt is not None:
|
||||
filename = '%s.txt' % channel
|
||||
filepath = os.path.join(current_dir, filename)
|
||||
print('--> Writing %s' % filename)
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(stable_txt)
|
||||
|
||||
# Generate per-milestone and per-milestone+platform JSON files.
|
||||
for channel in ('stable', 'beta'):
|
||||
for milestone in installer.get_milestones(channel=channel):
|
||||
channel_suffix = '_' + channel if channel != 'stable' else ''
|
||||
|
||||
# {milestone}.json
|
||||
filename = '%d%s.json' % (milestone, channel_suffix)
|
||||
filepath = os.path.join(current_dir, filename)
|
||||
print('--> Writing %s' % filename)
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(
|
||||
installer.generate_milestone_json(milestone,
|
||||
channel=channel,
|
||||
prettyprint=True))
|
||||
|
||||
# {milestone}_{platform}.json
|
||||
for platform in cef_json_builder.get_platforms():
|
||||
content = installer.generate_milestone_platform_json(milestone,
|
||||
platform,
|
||||
channel=channel,
|
||||
prettyprint=True)
|
||||
if content != '[]':
|
||||
filename = '%d_%s%s.json' % (milestone, platform, channel_suffix)
|
||||
filepath = os.path.join(current_dir, filename)
|
||||
print('--> Writing %s' % filename)
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
# Generate per-abi_hash+platform JSON files (Windows only).
|
||||
for channel in ('stable', 'beta'):
|
||||
for abi_hash in installer.get_abi_hashes(channel=channel):
|
||||
channel_suffix = '_' + channel if channel != 'stable' else ''
|
||||
for platform in ('windows32', 'windows64', 'windowsarm64'):
|
||||
content = installer.generate_abi_hash_platform_json(abi_hash,
|
||||
platform,
|
||||
channel=channel,
|
||||
prettyprint=True)
|
||||
if content != '[]':
|
||||
filename = '%s_%s%s.json' % (abi_hash, platform, channel_suffix)
|
||||
filepath = os.path.join(current_dir, filename)
|
||||
print('--> Writing %s' % filename)
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
@@ -12,6 +12,14 @@ from unittest.mock import patch, MagicMock
|
||||
|
||||
class TestCefJSONBuilder(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# Allow sandbox_compat queries for all milestones in tests.
|
||||
self._orig_min = cef_json_builder.MIN_SANDBOX_COMPAT_MILESTONE
|
||||
cef_json_builder.MIN_SANDBOX_COMPAT_MILESTONE = 0
|
||||
|
||||
def tearDown(self):
|
||||
cef_json_builder.MIN_SANDBOX_COMPAT_MILESTONE = self._orig_min
|
||||
|
||||
# Test CEF version number matching.
|
||||
def test_valid_version(self):
|
||||
# Old-style version numbers.
|
||||
@@ -212,8 +220,10 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
builder = cef_json_builder()
|
||||
|
||||
# Specify all values just in case the defaults change.
|
||||
expected = self._add_test_file(
|
||||
builder, platform='linux32', version=version, type='standard')
|
||||
expected = self._add_test_file(builder,
|
||||
platform='linux32',
|
||||
version=version,
|
||||
type='standard')
|
||||
|
||||
# No filter.
|
||||
files = builder.get_files()
|
||||
@@ -247,18 +257,22 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
self.assertEqual(len(files), 0)
|
||||
|
||||
# All filters.
|
||||
files = builder.get_files(
|
||||
platform='linux32', version=partial_version, type='standard')
|
||||
files = builder.get_files(platform='linux32',
|
||||
version=partial_version,
|
||||
type='standard')
|
||||
self.assertEqual(len(files), 1)
|
||||
self.assertEqual(expected, files[0])
|
||||
files = builder.get_files(
|
||||
platform='linux32', version=partial_version, type='minimal')
|
||||
files = builder.get_files(platform='linux32',
|
||||
version=partial_version,
|
||||
type='minimal')
|
||||
self.assertEqual(len(files), 0)
|
||||
files = builder.get_files(
|
||||
platform='linux32', version='3.2623', type='standard')
|
||||
files = builder.get_files(platform='linux32',
|
||||
version='3.2623',
|
||||
type='standard')
|
||||
self.assertEqual(len(files), 0)
|
||||
files = builder.get_files(
|
||||
platform='linux64', version=partial_version, type='standard')
|
||||
files = builder.get_files(platform='linux64',
|
||||
version=partial_version,
|
||||
type='standard')
|
||||
self.assertEqual(len(files), 0)
|
||||
|
||||
# Test add/get of multiple files.
|
||||
@@ -280,8 +294,10 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
for version in versions:
|
||||
for type in types:
|
||||
expected.append(
|
||||
self._add_test_file(
|
||||
builder, platform=platform, type=type, version=version))
|
||||
self._add_test_file(builder,
|
||||
platform=platform,
|
||||
type=type,
|
||||
version=version))
|
||||
|
||||
self._verify_write_read(builder)
|
||||
|
||||
@@ -307,8 +323,9 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
for platform in platforms:
|
||||
for version in versions:
|
||||
for type in types:
|
||||
files = builder.get_files(
|
||||
platform=platform, type=type, version=version)
|
||||
files = builder.get_files(platform=platform,
|
||||
type=type,
|
||||
version=version)
|
||||
self.assertEqual(len(files), 1)
|
||||
self.assertEqual(expected[idx], files[0])
|
||||
idx += 1
|
||||
@@ -325,8 +342,10 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
# Initial file versions.
|
||||
for platform in platforms:
|
||||
for type in types:
|
||||
self._add_test_file(
|
||||
builder, platform=platform, type=type, version=version)
|
||||
self._add_test_file(builder,
|
||||
platform=platform,
|
||||
type=type,
|
||||
version=version)
|
||||
|
||||
# No filter.
|
||||
files = builder.get_files()
|
||||
@@ -338,12 +357,11 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
for platform in platforms:
|
||||
for type in types:
|
||||
expected.append(
|
||||
self._add_test_file(
|
||||
builder,
|
||||
platform=platform,
|
||||
type=type,
|
||||
version=version,
|
||||
attrib_idx=1))
|
||||
self._add_test_file(builder,
|
||||
platform=platform,
|
||||
type=type,
|
||||
version=version,
|
||||
attrib_idx=1))
|
||||
|
||||
# No filter.
|
||||
files = builder.get_files()
|
||||
@@ -353,8 +371,9 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
idx = 0
|
||||
for platform in platforms:
|
||||
for type in types:
|
||||
files = builder.get_files(
|
||||
platform=platform, type=type, version=version)
|
||||
files = builder.get_files(platform=platform,
|
||||
type=type,
|
||||
version=version)
|
||||
self.assertEqual(len(files), 1)
|
||||
self.assertEqual(expected[idx], files[0])
|
||||
idx += 1
|
||||
@@ -371,8 +390,10 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
# Initial file versions.
|
||||
for platform in platforms:
|
||||
for type in types:
|
||||
self._add_test_file(
|
||||
builder, platform=platform, type=type, version=version)
|
||||
self._add_test_file(builder,
|
||||
platform=platform,
|
||||
type=type,
|
||||
version=version)
|
||||
|
||||
# No filter.
|
||||
files = builder.get_files()
|
||||
@@ -384,12 +405,11 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
for platform in platforms:
|
||||
for type in types:
|
||||
expected.append(
|
||||
self._add_test_file(
|
||||
builder,
|
||||
platform=platform,
|
||||
type=type,
|
||||
version=version,
|
||||
shouldfail=True))
|
||||
self._add_test_file(builder,
|
||||
platform=platform,
|
||||
type=type,
|
||||
version=version,
|
||||
shouldfail=True))
|
||||
|
||||
# No filter.
|
||||
files = builder.get_files()
|
||||
@@ -399,8 +419,9 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
idx = 0
|
||||
for platform in platforms:
|
||||
for type in types:
|
||||
files = builder.get_files(
|
||||
platform=platform, type=type, version=version)
|
||||
files = builder.get_files(platform=platform,
|
||||
type=type,
|
||||
version=version)
|
||||
self.assertEqual(len(files), 1)
|
||||
self.assertEqual(expected[idx], files[0])
|
||||
idx += 1
|
||||
@@ -495,21 +516,26 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
# Test CEF version to API version conversion.
|
||||
def test_cef_version_to_api_version(self):
|
||||
# New-style version numbers.
|
||||
self.assertEqual('13800',
|
||||
cef_json_builder.cef_version_to_api_version(
|
||||
'138.0.1+g62d140e+chromium-138.0.7204.0'))
|
||||
self.assertEqual('13801',
|
||||
cef_json_builder.cef_version_to_api_version(
|
||||
'138.1.0+g62d140e+chromium-138.0.7204.0'))
|
||||
self.assertEqual('13802',
|
||||
cef_json_builder.cef_version_to_api_version(
|
||||
'138.2.5+g62d140e+chromium-138.0.7204.0'))
|
||||
self.assertEqual('7401',
|
||||
cef_json_builder.cef_version_to_api_version(
|
||||
'74.1.0+g62d140e+chromium-74.0.3729.6'))
|
||||
self.assertEqual('10000',
|
||||
cef_json_builder.cef_version_to_api_version(
|
||||
'100.0.0+g62d140e+chromium-100.0.4896.0'))
|
||||
self.assertEqual(
|
||||
'13800',
|
||||
cef_json_builder.cef_version_to_api_version(
|
||||
'138.0.1+g62d140e+chromium-138.0.7204.0'))
|
||||
self.assertEqual(
|
||||
'13801',
|
||||
cef_json_builder.cef_version_to_api_version(
|
||||
'138.1.0+g62d140e+chromium-138.0.7204.0'))
|
||||
self.assertEqual(
|
||||
'13802',
|
||||
cef_json_builder.cef_version_to_api_version(
|
||||
'138.2.5+g62d140e+chromium-138.0.7204.0'))
|
||||
self.assertEqual(
|
||||
'7401',
|
||||
cef_json_builder.cef_version_to_api_version(
|
||||
'74.1.0+g62d140e+chromium-74.0.3729.6'))
|
||||
self.assertEqual(
|
||||
'10000',
|
||||
cef_json_builder.cef_version_to_api_version(
|
||||
'100.0.0+g62d140e+chromium-100.0.4896.0'))
|
||||
|
||||
# Old-style version numbers return None (no API versioning).
|
||||
self.assertIsNone(
|
||||
@@ -714,6 +740,35 @@ class TestCefJSONBuilder(unittest.TestCase):
|
||||
self.assertEqual(1, len(versions))
|
||||
self.assertEqual('abc123def4567890', versions[0].get('sandbox_compat'))
|
||||
|
||||
# Test get_short_version static method.
|
||||
def test_get_short_version(self):
|
||||
# New-style version returns short version.
|
||||
self.assertEqual(
|
||||
'137.3.5',
|
||||
cef_json_builder.get_short_version(
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6'))
|
||||
# Old-style version returns full string (no '+').
|
||||
self.assertEqual('3.2704.1414.g185cd6c',
|
||||
cef_json_builder.get_short_version('3.2704.1414.g185cd6c'))
|
||||
|
||||
# Test get_milestone static method.
|
||||
def test_get_milestone(self):
|
||||
# New-style version returns milestone.
|
||||
self.assertEqual(
|
||||
137,
|
||||
cef_json_builder.get_milestone(
|
||||
'137.3.5+g62d140e+chromium-137.0.7204.6'))
|
||||
# Old-style version returns 3.
|
||||
self.assertEqual(3, cef_json_builder.get_milestone('3.2704.1414.g185cd6c'))
|
||||
|
||||
# Test add/get of a signed type file.
|
||||
def test_add_signed_file(self):
|
||||
self._test_add_file('signed')
|
||||
|
||||
# Test add/get of a signed type file in beta channel.
|
||||
def test_add_signed_file_beta(self):
|
||||
self._test_add_file('signed', channel='beta')
|
||||
|
||||
# Test set_sandbox_compat with explicit value.
|
||||
def test_set_sandbox_compat_explicit(self):
|
||||
builder = cef_json_builder(silent=True)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"revoked_versions": []
|
||||
}
|
||||
Reference in new issue
Block a user