Compare commits

..

2 Commits

Author SHA1 Message Date
J. Nick Koston
c15bfd243a [web_server_idf] Reduce heap allocations by using stack buffers 2026-01-25 23:55:11 -10:00
sebcaps
1c9a9c7536 [mhz19] Fix Uninitialized var warning message (#13526) 2026-01-25 20:07:29 -10:00
8 changed files with 25 additions and 147 deletions

View File

@@ -46,7 +46,6 @@ jobs:
const BOT_COMMENT_MARKER = '<!-- auto-label-pr-bot -->';
const CODEOWNERS_MARKER = '<!-- codeowners-request -->';
const TOO_BIG_MARKER = '<!-- too-big-request -->';
const DEPRECATED_COMPONENT_MARKER = '<!-- deprecated-component-request -->';
const MANAGED_LABELS = [
'new-component',
@@ -70,8 +69,7 @@ jobs:
'new-feature',
'breaking-change',
'developer-breaking-change',
'code-quality',
'deprecated_component'
'code-quality'
];
const DOCS_PR_PATTERNS = [
@@ -384,75 +382,6 @@ jobs:
return labels;
}
// Strategy: Deprecated component detection
async function detectDeprecatedComponents() {
const labels = new Set();
const deprecatedInfo = [];
// Compile regex once for better performance
const componentFileRegex = /^esphome\/components\/([^\/]+)\/[^/]*\.py$/;
// Get files that are modified or added in components directory
const componentFiles = changedFiles.filter(file => componentFileRegex.test(file));
if (componentFiles.length === 0) {
return { labels, deprecatedInfo };
}
// Extract unique component names using the same regex
const components = new Set();
for (const file of componentFiles) {
const match = file.match(componentFileRegex);
if (match) {
components.add(match[1]);
}
}
// Get PR head SHA to fetch files from the PR branch
const prHeadSha = context.payload.pull_request.head.sha;
// Check each component's __init__.py for DEPRECATED_COMPONENT constant
for (const component of components) {
const initFile = `esphome/components/${component}/__init__.py`;
try {
// Fetch file content from PR head using GitHub API
const { data: fileData } = await github.rest.repos.getContent({
owner,
repo,
path: initFile,
ref: prHeadSha
});
// Decode base64 content
const content = Buffer.from(fileData.content, 'base64').toString('utf8');
// Look for DEPRECATED_COMPONENT = "message" or DEPRECATED_COMPONENT = 'message'
// Support single quotes, double quotes, and triple quotes (for multiline)
const doubleQuoteMatch = content.match(/DEPRECATED_COMPONENT\s*=\s*"""([\s\S]*?)"""/s) ||
content.match(/DEPRECATED_COMPONENT\s*=\s*"((?:[^"\\]|\\.)*)"/);
const singleQuoteMatch = content.match(/DEPRECATED_COMPONENT\s*=\s*'''([\s\S]*?)'''/s) ||
content.match(/DEPRECATED_COMPONENT\s*=\s*'((?:[^'\\]|\\.)*)'/);
const deprecatedMatch = doubleQuoteMatch || singleQuoteMatch;
if (deprecatedMatch) {
labels.add('deprecated_component');
deprecatedInfo.push({
component: component,
message: deprecatedMatch[1]
});
console.log(`Found deprecated component: ${component}`);
}
} catch (error) {
// Only log if it's not a simple "file not found" error (404)
if (error.status !== 404) {
console.log(`Error reading ${initFile}:`, error.message);
}
}
}
return { labels, deprecatedInfo };
}
// Strategy: Requirements detection
async function detectRequirements(allLabels) {
const labels = new Set();
@@ -489,26 +418,10 @@ jobs:
}
// Generate review messages
function generateReviewMessages(finalLabels, originalLabelCount, deprecatedInfo) {
function generateReviewMessages(finalLabels, originalLabelCount) {
const messages = [];
const prAuthor = context.payload.pull_request.user.login;
// Deprecated component message
if (finalLabels.includes('deprecated_component') && deprecatedInfo && deprecatedInfo.length > 0) {
let message = `${DEPRECATED_COMPONENT_MARKER}\n### ⚠️ Deprecated Component\n\n`;
message += `Hey there @${prAuthor},\n`;
message += `This PR modifies one or more deprecated components. Please be aware:\n\n`;
for (const info of deprecatedInfo) {
message += `#### Component: \`${info.component}\`\n`;
message += `${info.message}\n\n`;
}
message += `Consider migrating to the recommended alternative if applicable.`;
messages.push(message);
}
// Too big message
if (finalLabels.includes('too-big')) {
const testAdditions = prFiles
@@ -555,10 +468,10 @@ jobs:
}
// Handle reviews
async function handleReviews(finalLabels, originalLabelCount, deprecatedInfo) {
const reviewMessages = generateReviewMessages(finalLabels, originalLabelCount, deprecatedInfo);
async function handleReviews(finalLabels, originalLabelCount) {
const reviewMessages = generateReviewMessages(finalLabels, originalLabelCount);
const hasReviewableLabels = finalLabels.some(label =>
['too-big', 'needs-codeowners', 'deprecated_component'].includes(label)
['too-big', 'needs-codeowners'].includes(label)
);
const { data: reviews } = await github.rest.pulls.listReviews({
@@ -667,8 +580,7 @@ jobs:
actionsLabels,
codeOwnerLabels,
testLabels,
checkboxLabels,
deprecatedResult
checkboxLabels
] = await Promise.all([
detectMergeBranch(),
detectComponentPlatforms(apiData),
@@ -680,14 +592,9 @@ jobs:
detectGitHubActionsChanges(),
detectCodeOwner(),
detectTests(),
detectPRTemplateCheckboxes(),
detectDeprecatedComponents()
detectPRTemplateCheckboxes()
]);
// Extract deprecated component info
const deprecatedLabels = deprecatedResult.labels;
const deprecatedInfo = deprecatedResult.deprecatedInfo;
// Combine all labels
const allLabels = new Set([
...branchLabels,
@@ -700,8 +607,7 @@ jobs:
...actionsLabels,
...codeOwnerLabels,
...testLabels,
...checkboxLabels,
...deprecatedLabels
...checkboxLabels
]);
// Detect requirements based on all other labels
@@ -732,7 +638,7 @@ jobs:
console.log('Computed labels:', finalLabels.join(', '));
// Handle reviews
await handleReviews(finalLabels, originalLabelCount, deprecatedInfo);
await handleReviews(finalLabels, originalLabelCount);
// Apply labels
if (finalLabels.length > 0) {

View File

@@ -155,6 +155,9 @@ void MHZ19Component::dump_config() {
case MHZ19_DETECTION_RANGE_0_10000PPM:
range_str = "0 to 10000ppm";
break;
default:
range_str = "default";
break;
}
ESP_LOGCONFIG(TAG, " Detection range: %s", range_str);
}

View File

@@ -1,17 +0,0 @@
# Test Deprecated Component
This is a test component to validate the `deprecated_component` label functionality in the auto-label workflow.
## Purpose
This component demonstrates how the `DEPRECATED_COMPONENT` constant should be used:
1. The component is still functional and usable
2. It's deprecated and not actively maintained
3. Users should migrate to an alternative when possible
## Usage
This is different from components that use `cv.invalid()`, which are completely unusable.
Components with `DEPRECATED_COMPONENT` are still functional but discouraged for new projects.

View File

@@ -1,6 +0,0 @@
"""Test deprecated component for validation."""
CODEOWNERS = ["@test"]
DEPRECATED_COMPONENT = "This component is deprecated and will be removed in a future release. Please use the newer_component instead."
# Component is still functional but deprecated

View File

@@ -1,5 +0,0 @@
"""Test sensor platform for deprecated component."""
import esphome.config_validation as cv
# This is a simple placeholder - component still works
CONFIG_SCHEMA = cv.Schema({})

View File

@@ -1,2 +1 @@
CODEOWNERS = ["@clydebarrow"]
DEPRECATED_COMPONENT = "The waveshare_epaper component is deprecated and no new models will be added. For new epaper displays use the epaper_spi component"

View File

@@ -78,11 +78,8 @@ optional<std::string> query_key_value(const std::string &query_url, const std::s
return {};
}
auto val = std::unique_ptr<char[]>(new char[query_url.size()]);
if (!val) {
ESP_LOGE(TAG, "Not enough memory to the query key value");
return {};
}
// Use stack buffer for typical query strings, heap fallback for large ones
SmallBufferWithHeapFallback<256, char> val(query_url.size());
if (httpd_query_key_value(query_url.c_str(), key.c_str(), val.get(), query_url.size()) != ESP_OK) {
return {};

View File

@@ -352,14 +352,15 @@ bool AsyncWebServerRequest::authenticate(const char *username, const char *passw
memcpy(user_info + user_len + 1, password, pass_len);
user_info[user_info_len] = '\0';
size_t n = 0, out;
esp_crypto_base64_encode(nullptr, 0, &n, reinterpret_cast<const uint8_t *>(user_info), user_info_len);
auto digest = std::unique_ptr<char[]>(new char[n + 1]);
esp_crypto_base64_encode(reinterpret_cast<uint8_t *>(digest.get()), n, &out,
// Base64 output size is ceil(input_len * 4/3) + 1, with input bounded to 256 bytes
// max output is ceil(256 * 4/3) + 1 = 343 bytes, use 350 for safety
constexpr size_t max_digest_len = 350;
char digest[max_digest_len];
size_t out;
esp_crypto_base64_encode(reinterpret_cast<uint8_t *>(digest), max_digest_len, &out,
reinterpret_cast<const uint8_t *>(user_info), user_info_len);
return strcmp(digest.get(), auth_str + auth_prefix_len) == 0;
return strcmp(digest, auth_str + auth_prefix_len) == 0;
}
void AsyncWebServerRequest::requestAuthentication(const char *realm) const {
@@ -869,12 +870,12 @@ esp_err_t AsyncWebServer::handle_multipart_upload_(httpd_req_t *r, const char *c
}
});
// Process data
std::unique_ptr<char[]> buffer(new char[MULTIPART_CHUNK_SIZE]);
// Process data - use stack buffer to avoid heap allocation
char buffer[MULTIPART_CHUNK_SIZE];
size_t bytes_since_yield = 0;
for (size_t remaining = r->content_len; remaining > 0;) {
int recv_len = httpd_req_recv(r, buffer.get(), std::min(remaining, MULTIPART_CHUNK_SIZE));
int recv_len = httpd_req_recv(r, buffer, std::min(remaining, MULTIPART_CHUNK_SIZE));
if (recv_len <= 0) {
httpd_resp_send_err(r, recv_len == HTTPD_SOCK_ERR_TIMEOUT ? HTTPD_408_REQ_TIMEOUT : HTTPD_400_BAD_REQUEST,
@@ -882,7 +883,7 @@ esp_err_t AsyncWebServer::handle_multipart_upload_(httpd_req_t *r, const char *c
return recv_len == HTTPD_SOCK_ERR_TIMEOUT ? ESP_ERR_TIMEOUT : ESP_FAIL;
}
if (reader->parse(buffer.get(), recv_len) != static_cast<size_t>(recv_len)) {
if (reader->parse(buffer, recv_len) != static_cast<size_t>(recv_len)) {
ESP_LOGW(TAG, "Multipart parser error");
httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
return ESP_FAIL;