Skip to content

Commit 142a5d4

Browse files
Merge branch 'main' into feature/allow-exact-match
2 parents b1901bc + 3d71906 commit 142a5d4

6 files changed

Lines changed: 143 additions & 14 deletions

File tree

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bundled/scripts/noConfigScripts/debugjava text eol=lf

bundled/scripts/noConfigScripts/debugjava

100644100755
File mode changed.

package-lock.json

Lines changed: 0 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1388,7 +1388,6 @@
13881388
"@types/lodash": "^4.17.13",
13891389
"@types/mocha": "^10.0.9",
13901390
"@types/node": "^14.18.63",
1391-
"@types/uuid": "^8.3.4",
13921391
"@types/vscode": "1.95.0",
13931392
"@vscode/test-electron": "^3.1.0",
13941393
"mocha": "^10.8.2",

src/noConfigDebugInit.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,32 @@ import { applyAppendIfChanged, applyReplaceIfChanged } from "./envVarSync";
1313

1414
const ENV_VAR_COLLECTION_DESCRIPTION = "Java No-Config Debug";
1515

16+
/**
17+
* Ensures the POSIX no-config debug wrapper can be invoked from a terminal.
18+
*
19+
* Official VSIX packages are built on Windows, where executable bits are not
20+
* preserved. Repair the installed script when the extension activates on a
21+
* POSIX platform, while preserving all existing permissions and avoiding an
22+
* unnecessary chmod when the owner can already execute it.
23+
*
24+
* @param scriptPath - The installed debugjava wrapper path.
25+
* @param platform - The current operating system platform.
26+
*/
27+
export async function ensureDebugJavaScriptExecutable(
28+
scriptPath: string,
29+
platform: NodeJS.Platform = process.platform,
30+
): Promise<void> {
31+
if (platform === "win32") {
32+
return;
33+
}
34+
35+
const permissions = (await fs.promises.stat(scriptPath)).mode % 0o10000;
36+
const ownerPermissions = Math.floor(permissions / 0o100);
37+
if (ownerPermissions % 2 === 0) {
38+
await fs.promises.chmod(scriptPath, permissions + 0o100);
39+
}
40+
}
41+
1642
/**
1743
* Registers the configuration-less debugging setup for the extension.
1844
*
@@ -104,6 +130,16 @@ export async function registerNoConfigDebug(
104130
}
105131

106132
const noConfigScriptsDir = path.join(extPath, 'bundled', 'scripts', 'noConfigScripts');
133+
const debugJavaScriptPath = path.join(noConfigScriptsDir, "debugjava");
134+
try {
135+
await ensureDebugJavaScriptExecutable(debugJavaScriptPath);
136+
} catch (err) {
137+
const error: Error = {
138+
name: "NoConfigDebugError",
139+
message: `[Java Debug] Failed to make debugjava executable: ${err}`,
140+
};
141+
sendError(error);
142+
}
107143
applyAppendIfChanged(collection, 'PATH', buildNoConfigPathAppendValue(noConfigScriptsDir));
108144

109145
// create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written

test/noConfigDebugInit.test.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
import * as assert from "assert";
5+
import * as fs from "fs";
6+
import * as os from "os";
7+
import * as path from "path";
8+
9+
import { ensureDebugJavaScriptExecutable } from "../src/noConfigDebugInit";
10+
11+
suite("No-Config Debug scripts", () => {
12+
test("the bundled POSIX wrapper uses LF and is executable", async () => {
13+
const scriptPath = path.resolve(
14+
__dirname,
15+
"../../bundled/scripts/noConfigScripts/debugjava",
16+
);
17+
const contents = await fs.promises.readFile(scriptPath, "utf8");
18+
19+
assert.ok(contents.startsWith("#!/bin/bash\n"));
20+
assert.strictEqual(contents.includes("\r"), false);
21+
22+
if (process.platform !== "win32") {
23+
const mode = (await fs.promises.stat(scriptPath)).mode % 0o1000;
24+
assert.strictEqual(mode, 0o755);
25+
}
26+
});
27+
28+
test("adds owner execute permission without changing other permissions", async () => {
29+
if (process.platform === "win32") {
30+
return;
31+
}
32+
33+
const tempDir = await fs.promises.mkdtemp(
34+
path.join(os.tmpdir(), "vscode-java-debug-"),
35+
);
36+
const scriptPath = path.join(tempDir, "debugjava");
37+
38+
try {
39+
await fs.promises.writeFile(scriptPath, "#!/usr/bin/env bash\n");
40+
41+
const cases = [
42+
{ initial: 0o444, expected: 0o544 },
43+
{ initial: 0o600, expected: 0o700 },
44+
{ initial: 0o644, expected: 0o744 },
45+
{ initial: 0o6444, expected: 0o6544 },
46+
];
47+
for (const testCase of cases) {
48+
await fs.promises.chmod(scriptPath, testCase.initial);
49+
await ensureDebugJavaScriptExecutable(scriptPath, "linux");
50+
51+
const mode = (await fs.promises.stat(scriptPath)).mode % 0o10000;
52+
assert.strictEqual(mode, testCase.expected);
53+
}
54+
} finally {
55+
await fs.promises.rm(tempDir, { recursive: true, force: true });
56+
}
57+
});
58+
59+
test("does not chmod an already owner-executable POSIX wrapper", async () => {
60+
if (process.platform === "win32") {
61+
return;
62+
}
63+
64+
const tempDir = await fs.promises.mkdtemp(
65+
path.join(os.tmpdir(), "vscode-java-debug-"),
66+
);
67+
const scriptPath = path.join(tempDir, "debugjava");
68+
69+
try {
70+
await fs.promises.writeFile(scriptPath, "#!/usr/bin/env bash\n");
71+
72+
const originalChmod = fs.promises.chmod;
73+
let chmodCalls = 0;
74+
fs.promises.chmod = async (...args): Promise<void> => {
75+
chmodCalls += 1;
76+
return originalChmod(...args);
77+
};
78+
try {
79+
const executableModes = [0o700, 0o750, 0o775];
80+
for (const mode of executableModes) {
81+
await originalChmod(scriptPath, mode);
82+
await ensureDebugJavaScriptExecutable(scriptPath, "darwin");
83+
const actualMode =
84+
(await fs.promises.stat(scriptPath)).mode % 0o1000;
85+
assert.strictEqual(actualMode, mode);
86+
}
87+
} finally {
88+
fs.promises.chmod = originalChmod;
89+
}
90+
91+
assert.strictEqual(chmodCalls, 0);
92+
} finally {
93+
await fs.promises.rm(tempDir, { recursive: true, force: true });
94+
}
95+
});
96+
97+
test("does not change wrapper permissions on Windows", async () => {
98+
const missingScriptPath = path.join(
99+
os.tmpdir(),
100+
"vscode-java-debug-missing",
101+
"debugjava",
102+
);
103+
104+
await ensureDebugJavaScriptExecutable(missingScriptPath, "win32");
105+
});
106+
});

0 commit comments

Comments
 (0)