Skip to content

Commit 52b091a

Browse files
antonisclaude
andcommitted
feat(core): Support SENTRY_RELEASE and SENTRY_DIST env vars in build scripts
The build scripts (`sentry-xcode.sh` and `sentry.gradle`) already support overriding `environment` in `sentry.options.json` via `SENTRY_ENVIRONMENT`. This extends the same pattern to `SENTRY_RELEASE` and `SENTRY_DIST`. When using native auto-init, users with custom release formats in `Sentry.init()` need a way to set matching values in the options file at build time to avoid split releases (pre-JS crashes on one release, post-JS events on another). These env vars are already used by `sentry-cli` for source maps upload, so setting them now aligns both source maps and native init in one step. Closes #6069 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 98b00f0 commit 52b091a

3 files changed

Lines changed: 136 additions & 13 deletions

File tree

packages/core/scripts/sentry-xcode.sh

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,21 @@ if [ "$SENTRY_COPY_OPTIONS_FILE" = true ]; then
105105
else
106106
cp "$SENTRY_OPTIONS_FILE_PATH" "$SENTRY_OPTIONS_FILE_DESTINATION_PATH"
107107

108-
if [ -n "$SENTRY_ENVIRONMENT" ]; then
108+
if [ -n "$SENTRY_ENVIRONMENT" ] || [ -n "$SENTRY_RELEASE" ] || [ -n "$SENTRY_DIST" ]; then
109109
if "$LOCAL_NODE_BINARY" -e "
110110
var fs = require('fs');
111111
var destPath = process.argv[1];
112112
var opts = JSON.parse(fs.readFileSync(destPath, 'utf8'));
113-
opts.environment = process.env.SENTRY_ENVIRONMENT;
113+
if (process.env.SENTRY_ENVIRONMENT) { opts.environment = process.env.SENTRY_ENVIRONMENT; }
114+
if (process.env.SENTRY_RELEASE) { opts.release = process.env.SENTRY_RELEASE; }
115+
if (process.env.SENTRY_DIST) { opts.dist = process.env.SENTRY_DIST; }
114116
fs.writeFileSync(destPath, JSON.stringify(opts));
115117
" -- "$SENTRY_OPTIONS_FILE_DESTINATION_PATH" 2>/dev/null; then
116-
echo "[Sentry] Overriding 'environment' from SENTRY_ENVIRONMENT environment variable"
118+
[ -n "$SENTRY_ENVIRONMENT" ] && echo "[Sentry] Overriding 'environment' from SENTRY_ENVIRONMENT environment variable"
119+
[ -n "$SENTRY_RELEASE" ] && echo "[Sentry] Overriding 'release' from SENTRY_RELEASE environment variable"
120+
[ -n "$SENTRY_DIST" ] && echo "[Sentry] Overriding 'dist' from SENTRY_DIST environment variable"
117121
else
118-
echo "[Sentry] Failed to override environment, copied file as-is." 1>&2
122+
echo "[Sentry] Failed to override options from environment variables, copied file as-is." 1>&2
119123
fi
120124
fi
121125
echo "[Sentry] Copied $SENTRY_OPTIONS_FILE_PATH to $SENTRY_OPTIONS_FILE_DESTINATION_PATH"

packages/core/sentry.gradle

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,21 @@ tasks.register("copySentryJsonConfiguration") {
4646
}
4747

4848
def sentryEnv = System.getenv('SENTRY_ENVIRONMENT')
49-
if (sentryEnv) {
49+
def sentryRelease = System.getenv('SENTRY_RELEASE')
50+
def sentryDist = System.getenv('SENTRY_DIST')
51+
if (sentryEnv || sentryRelease || sentryDist) {
5052
try {
5153
def destFile = new File(androidAssetsDir, configFile)
5254
def content = new groovy.json.JsonSlurper().parseText(destFile.text)
53-
content.environment = sentryEnv
55+
if (sentryEnv) { content.environment = sentryEnv }
56+
if (sentryRelease) { content.release = sentryRelease }
57+
if (sentryDist) { content.dist = sentryDist }
5458
destFile.text = groovy.json.JsonOutput.toJson(content)
55-
logger.lifecycle("Overriding 'environment' from SENTRY_ENVIRONMENT environment variable")
59+
if (sentryEnv) { logger.lifecycle("Overriding 'environment' from SENTRY_ENVIRONMENT environment variable") }
60+
if (sentryRelease) { logger.lifecycle("Overriding 'release' from SENTRY_RELEASE environment variable") }
61+
if (sentryDist) { logger.lifecycle("Overriding 'dist' from SENTRY_DIST environment variable") }
5662
} catch (Exception e) {
57-
logger.warn("Failed to override environment in ${configFile}: ${e.message}. Copied file as-is.")
63+
logger.warn("Failed to override options in ${configFile}: ${e.message}. Copied file as-is.")
5864
}
5965
}
6066
logger.lifecycle("Copied ${configFile} to Android assets")

packages/core/test/scripts/sentry-xcode-scripts.test.ts

Lines changed: 118 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -531,8 +531,8 @@ describe('sentry-xcode.sh', () => {
531531
});
532532
});
533533

534-
describe('sentry.options.json SENTRY_ENVIRONMENT override', () => {
535-
it('copies file without modification when SENTRY_ENVIRONMENT is not set', () => {
534+
describe('sentry.options.json environment variable overrides', () => {
535+
it('copies file without modification when no override env vars are set', () => {
536536
const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123', environment: 'production' });
537537
const optionsFile = path.join(tempDir, 'sentry.options.json');
538538
fs.writeFileSync(optionsFile, optionsContent);
@@ -575,14 +575,92 @@ describe('sentry-xcode.sh', () => {
575575
});
576576

577577
expect(result.exitCode).toBe(0);
578-
expect(result.stdout).toContain('Overriding');
578+
expect(result.stdout).toContain("Overriding 'environment' from SENTRY_ENVIRONMENT");
579579
const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json');
580580
const copied = JSON.parse(fs.readFileSync(destPath, 'utf8'));
581581
expect(copied.environment).toBe('staging');
582582
expect(copied.dsn).toBe('https://key@sentry.io/123');
583583
});
584584

585-
it('does not modify the source sentry.options.json', () => {
585+
it('overrides release from SENTRY_RELEASE env var', () => {
586+
const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123' });
587+
const optionsFile = path.join(tempDir, 'sentry.options.json');
588+
fs.writeFileSync(optionsFile, optionsContent);
589+
590+
const buildDir = path.join(tempDir, 'build');
591+
const resourcesPath = 'Resources';
592+
fs.mkdirSync(path.join(buildDir, resourcesPath), { recursive: true });
593+
594+
const result = runScript({
595+
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
596+
SENTRY_COPY_OPTIONS_FILE: 'true',
597+
SENTRY_OPTIONS_FILE_PATH: optionsFile,
598+
CONFIGURATION_BUILD_DIR: buildDir,
599+
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
600+
SENTRY_RELEASE: 'my-app@1.0.0+42',
601+
});
602+
603+
expect(result.exitCode).toBe(0);
604+
expect(result.stdout).toContain("Overriding 'release' from SENTRY_RELEASE");
605+
const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json');
606+
const copied = JSON.parse(fs.readFileSync(destPath, 'utf8'));
607+
expect(copied.release).toBe('my-app@1.0.0+42');
608+
expect(copied.dsn).toBe('https://key@sentry.io/123');
609+
});
610+
611+
it('overrides existing release value from SENTRY_RELEASE env var', () => {
612+
const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123', release: 'old@1.0.0', dist: '1' });
613+
const optionsFile = path.join(tempDir, 'sentry.options.json');
614+
fs.writeFileSync(optionsFile, optionsContent);
615+
616+
const buildDir = path.join(tempDir, 'build');
617+
const resourcesPath = 'Resources';
618+
fs.mkdirSync(path.join(buildDir, resourcesPath), { recursive: true });
619+
620+
const result = runScript({
621+
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
622+
SENTRY_COPY_OPTIONS_FILE: 'true',
623+
SENTRY_OPTIONS_FILE_PATH: optionsFile,
624+
CONFIGURATION_BUILD_DIR: buildDir,
625+
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
626+
SENTRY_RELEASE: 'new@2.0.0',
627+
SENTRY_DIST: '2',
628+
});
629+
630+
expect(result.exitCode).toBe(0);
631+
const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json');
632+
const copied = JSON.parse(fs.readFileSync(destPath, 'utf8'));
633+
expect(copied.release).toBe('new@2.0.0');
634+
expect(copied.dist).toBe('2');
635+
});
636+
637+
it('overrides dist from SENTRY_DIST env var', () => {
638+
const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123' });
639+
const optionsFile = path.join(tempDir, 'sentry.options.json');
640+
fs.writeFileSync(optionsFile, optionsContent);
641+
642+
const buildDir = path.join(tempDir, 'build');
643+
const resourcesPath = 'Resources';
644+
fs.mkdirSync(path.join(buildDir, resourcesPath), { recursive: true });
645+
646+
const result = runScript({
647+
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
648+
SENTRY_COPY_OPTIONS_FILE: 'true',
649+
SENTRY_OPTIONS_FILE_PATH: optionsFile,
650+
CONFIGURATION_BUILD_DIR: buildDir,
651+
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
652+
SENTRY_DIST: '42',
653+
});
654+
655+
expect(result.exitCode).toBe(0);
656+
expect(result.stdout).toContain("Overriding 'dist' from SENTRY_DIST");
657+
const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json');
658+
const copied = JSON.parse(fs.readFileSync(destPath, 'utf8'));
659+
expect(copied.dist).toBe('42');
660+
expect(copied.dsn).toBe('https://key@sentry.io/123');
661+
});
662+
663+
it('overrides release, dist, and environment together', () => {
586664
const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123', environment: 'production' });
587665
const optionsFile = path.join(tempDir, 'sentry.options.json');
588666
fs.writeFileSync(optionsFile, optionsContent);
@@ -591,17 +669,52 @@ describe('sentry-xcode.sh', () => {
591669
const resourcesPath = 'Resources';
592670
fs.mkdirSync(path.join(buildDir, resourcesPath), { recursive: true });
593671

672+
const result = runScript({
673+
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
674+
SENTRY_COPY_OPTIONS_FILE: 'true',
675+
SENTRY_OPTIONS_FILE_PATH: optionsFile,
676+
CONFIGURATION_BUILD_DIR: buildDir,
677+
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
678+
SENTRY_ENVIRONMENT: 'staging',
679+
SENTRY_RELEASE: 'my-app@2.0.0+10',
680+
SENTRY_DIST: '10',
681+
});
682+
683+
expect(result.exitCode).toBe(0);
684+
expect(result.stdout).toContain("Overriding 'environment' from SENTRY_ENVIRONMENT");
685+
expect(result.stdout).toContain("Overriding 'release' from SENTRY_RELEASE");
686+
expect(result.stdout).toContain("Overriding 'dist' from SENTRY_DIST");
687+
const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json');
688+
const copied = JSON.parse(fs.readFileSync(destPath, 'utf8'));
689+
expect(copied.environment).toBe('staging');
690+
expect(copied.release).toBe('my-app@2.0.0+10');
691+
expect(copied.dist).toBe('10');
692+
});
693+
694+
it('does not modify the source file when overriding', () => {
695+
const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123', release: 'original@1.0.0', environment: 'production' });
696+
const optionsFile = path.join(tempDir, 'sentry.options.json');
697+
fs.writeFileSync(optionsFile, optionsContent);
698+
699+
const buildDir = path.join(tempDir, 'build');
700+
const resourcesPath = 'Resources';
701+
fs.mkdirSync(path.join(buildDir, resourcesPath), { recursive: true });
702+
594703
runScript({
595704
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
596705
SENTRY_COPY_OPTIONS_FILE: 'true',
597706
SENTRY_OPTIONS_FILE_PATH: optionsFile,
598707
CONFIGURATION_BUILD_DIR: buildDir,
599708
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
600709
SENTRY_ENVIRONMENT: 'staging',
710+
SENTRY_RELEASE: 'override@2.0.0',
711+
SENTRY_DIST: '99',
601712
});
602713

603714
const source = JSON.parse(fs.readFileSync(optionsFile, 'utf8'));
604715
expect(source.environment).toBe('production');
716+
expect(source.release).toBe('original@1.0.0');
717+
expect(source.dist).toBeUndefined();
605718
});
606719

607720
it('falls back to plain copy when sentry.options.json contains invalid JSON', () => {
@@ -618,7 +731,7 @@ describe('sentry-xcode.sh', () => {
618731
SENTRY_OPTIONS_FILE_PATH: optionsFile,
619732
CONFIGURATION_BUILD_DIR: buildDir,
620733
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
621-
SENTRY_ENVIRONMENT: 'staging',
734+
SENTRY_RELEASE: 'my-app@1.0.0',
622735
});
623736

624737
expect(result.exitCode).toBe(0);

0 commit comments

Comments
 (0)