Skip to content

Commit 466fb3b

Browse files
authored
rsync support in deploySteps (#9808)
1 parent 08fd621 commit 466fb3b

File tree

5 files changed

+195
-46
lines changed

5 files changed

+195
-46
lines changed

Extension/package.json

Lines changed: 102 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2333,21 +2333,21 @@
23332333
"markdownDescription": "%c_cpp.configuration.simplifyStructuredComments.markdownDescription%",
23342334
"scope": "application"
23352335
},
2336-
"C_Cpp.doxygen.generateOnType":{
2336+
"C_Cpp.doxygen.generateOnType": {
23372337
"type": "boolean",
23382338
"default": true,
23392339
"description": "%c_cpp.configuration.doxygen.generateOnType.description%",
23402340
"scope": "resource"
23412341
},
23422342
"C_Cpp.doxygen.generatedStyle": {
23432343
"type": "string",
2344-
"enum":[
2344+
"enum": [
23452345
"///",
23462346
"/**",
23472347
"/*!",
23482348
"//!"
23492349
],
2350-
"default":"///",
2350+
"default": "///",
23512351
"description": "%c_cpp.configuration.doxygen.generatedStyle.description%",
23522352
"scope": "resource"
23532353
},
@@ -2897,7 +2897,7 @@
28972897
"icon": "$(debug-configure)"
28982898
},
28992899
{
2900-
"command": "C_Cpp.GenerateDoxygenComment",
2900+
"command": "C_Cpp.GenerateDoxygenComment",
29012901
"title": "%c_cpp.command.GenerateDoxygenComment.title%",
29022902
"category": "C/C++"
29032903
}
@@ -3355,7 +3355,7 @@
33553355
"anyOf": [
33563356
{
33573357
"type": "object",
3358-
"description": "%c_cpp.debuggers.deploySteps.scp.description%",
3358+
"description": "%c_cpp.debuggers.deploySteps.copyFile.description%",
33593359
"default": {},
33603360
"required": [
33613361
"type",
@@ -3366,10 +3366,11 @@
33663366
"properties": {
33673367
"type": {
33683368
"type": "string",
3369-
"description": "%c_cpp.debuggers.deploySteps.scp.description%",
3369+
"description": "%c_cpp.debuggers.deploySteps.copyFile.description%",
33703370
"default": "",
33713371
"enum": [
3372-
"scp"
3372+
"scp",
3373+
"rsync"
33733374
]
33743375
},
33753376
"files": {
@@ -3384,7 +3385,7 @@
33843385
}
33853386
}
33863387
],
3387-
"description": "%c_cpp.debuggers.deploySteps.scp.files.description%",
3388+
"description": "%c_cpp.debuggers.deploySteps.copyFile.files.description%",
33883389
"default": ""
33893390
},
33903391
"host": {
@@ -3512,19 +3513,57 @@
35123513
},
35133514
"targetDir": {
35143515
"type": "string",
3515-
"description": "%c_cpp.debuggers.deploySteps.scp.targetDir.description%",
3516+
"description": "%c_cpp.debuggers.deploySteps.copyFile.targetDir.description%",
35163517
"default": ""
35173518
},
3518-
"scpPath": {
3519-
"type": "string",
3520-
"description": "%c_cpp.debuggers.deploySteps.scp.scpPath.description%",
3521-
"default": ""
3519+
"recursive": {
3520+
"type": "boolean",
3521+
"description": "%c_cpp.debuggers.deploySteps.copyFile.recursive.description%",
3522+
"default": "true"
35223523
},
35233524
"debug": {
35243525
"type": "boolean",
35253526
"description": "%c_cpp.debuggers.deploySteps.debug%"
35263527
}
3527-
}
3528+
},
3529+
"allOf": [
3530+
{
3531+
"if": {
3532+
"properties": {
3533+
"type": {
3534+
"const": "scp"
3535+
}
3536+
}
3537+
},
3538+
"then": {
3539+
"properties": {
3540+
"scpPath": {
3541+
"type": "string",
3542+
"description": "%c_cpp.debuggers.deploySteps.copyFile.scpPath.description%",
3543+
"default": ""
3544+
}
3545+
}
3546+
}
3547+
},
3548+
{
3549+
"if": {
3550+
"properties": {
3551+
"type": {
3552+
"const": "rsync"
3553+
}
3554+
}
3555+
},
3556+
"then": {
3557+
"properties": {
3558+
"rsyncPath": {
3559+
"type": "string",
3560+
"description": "%c_cpp.debuggers.deploySteps.copyFile.rsyncPath.description%",
3561+
"default": ""
3562+
}
3563+
}
3564+
}
3565+
}
3566+
]
35283567
},
35293568
{
35303569
"type": "object",
@@ -4034,7 +4073,7 @@
40344073
"anyOf": [
40354074
{
40364075
"type": "object",
4037-
"description": "%c_cpp.debuggers.deploySteps.scp.description%",
4076+
"description": "%c_cpp.debuggers.deploySteps.copyFile.description%",
40384077
"default": {},
40394078
"required": [
40404079
"type",
@@ -4045,10 +4084,11 @@
40454084
"properties": {
40464085
"type": {
40474086
"type": "string",
4048-
"description": "%c_cpp.debuggers.deploySteps.scp.description%",
4087+
"description": "%c_cpp.debuggers.deploySteps.copyFile.description%",
40494088
"default": "",
40504089
"enum": [
4051-
"scp"
4090+
"scp",
4091+
"rsync"
40524092
]
40534093
},
40544094
"files": {
@@ -4063,7 +4103,7 @@
40634103
}
40644104
}
40654105
],
4066-
"description": "%c_cpp.debuggers.deploySteps.scp.files.description%",
4106+
"description": "%c_cpp.debuggers.deploySteps.copyFile.files.description%",
40674107
"default": ""
40684108
},
40694109
"host": {
@@ -4191,19 +4231,57 @@
41914231
},
41924232
"targetDir": {
41934233
"type": "string",
4194-
"description": "%c_cpp.debuggers.deploySteps.scp.targetDir.description%",
4234+
"description": "%c_cpp.debuggers.deploySteps.copyFile.targetDir.description%",
41954235
"default": ""
41964236
},
4197-
"scpPath": {
4198-
"type": "string",
4199-
"description": "%c_cpp.debuggers.deploySteps.scp.scpPath.description%",
4200-
"default": ""
4237+
"recursive": {
4238+
"type": "boolean",
4239+
"description": "%c_cpp.debuggers.deploySteps.copyFile.recursive.description%",
4240+
"default": "true"
42014241
},
42024242
"debug": {
42034243
"type": "boolean",
42044244
"description": "%c_cpp.debuggers.deploySteps.debug%"
42054245
}
4206-
}
4246+
},
4247+
"allOf": [
4248+
{
4249+
"if": {
4250+
"properties": {
4251+
"type": {
4252+
"const": "scp"
4253+
}
4254+
}
4255+
},
4256+
"then": {
4257+
"properties": {
4258+
"scpPath": {
4259+
"type": "string",
4260+
"description": "%c_cpp.debuggers.deploySteps.copyFile.scpPath.description%",
4261+
"default": ""
4262+
}
4263+
}
4264+
}
4265+
},
4266+
{
4267+
"if": {
4268+
"properties": {
4269+
"type": {
4270+
"const": "rsync"
4271+
}
4272+
}
4273+
},
4274+
"then": {
4275+
"properties": {
4276+
"rsyncPath": {
4277+
"type": "string",
4278+
"description": "%c_cpp.debuggers.deploySteps.copyFile.rsyncPath.description%",
4279+
"default": ""
4280+
}
4281+
}
4282+
}
4283+
}
4284+
]
42074285
},
42084286
{
42094287
"type": "object",

Extension/package.nls.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,10 +321,12 @@
321321
"c_cpp.debuggers.host.localForward.localSocket.description": "Local socket",
322322
"c_cpp.debuggers.host.localForward.remoteSocket.description": "Remote socket",
323323
"c_cpp.debuggers.deploySteps.description": "Steps needed to deploy the application. Order matters.",
324-
"c_cpp.debuggers.deploySteps.scp.description": "Copy files using SCP.",
325-
"c_cpp.debuggers.deploySteps.scp.files.description": "Files to be copied via SCP. Supports path pattern.",
326-
"c_cpp.debuggers.deploySteps.scp.targetDir.description": "Target directory.",
327-
"c_cpp.debuggers.deploySteps.scp.scpPath.description": "Optional full path to SCP. Assumes SCP is on PATH if not specified",
324+
"c_cpp.debuggers.deploySteps.copyFile.description": "Copy files using SCP or rsync.",
325+
"c_cpp.debuggers.deploySteps.copyFile.files.description": "Files to be copied. Supports path pattern.",
326+
"c_cpp.debuggers.deploySteps.copyFile.targetDir.description": "Target directory.",
327+
"c_cpp.debuggers.deploySteps.copyFile.recursive.description": "If true, copies folders recursively.",
328+
"c_cpp.debuggers.deploySteps.copyFile.scpPath.description": "Optional full path to SCP. Assumes SCP is on PATH if not specified",
329+
"c_cpp.debuggers.deploySteps.copyFile.rsyncPath.description": "Optional full path to rsync. Assumes rsync is on PATH if not specified",
328330
"c_cpp.debuggers.deploySteps.debug": "If true, skip when starting without debugging. If false, skip when starting debugging. If undefined, never skip.",
329331
"c_cpp.debuggers.deploySteps.ssh.description": "SSH command step.",
330332
"c_cpp.debuggers.deploySteps.ssh.command.description": "Command to be executed via SSH. The command after '-c' in SSH command.",

Extension/src/Debugger/configurationProvider.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { Environment, ParsedEnvironmentFile } from './ParsedEnvironmentFile';
2424
import { CppSettings, OtherSettings } from '../LanguageServer/settings';
2525
import { configPrefix } from '../LanguageServer/extension';
2626
import { expandAllStrings, ExpansionOptions, ExpansionVars } from '../expand';
27-
import { scp, ssh } from '../SSH/commands';
27+
import { rsync, scp, ssh } from '../SSH/commands';
2828
import * as glob from 'glob';
2929
import { promisify } from 'util';
3030
import { AttachItemsProvider, AttachPicker, RemoteAttachPicker } from './attachToProcess';
@@ -35,6 +35,7 @@ const localize: nls.LocalizeFunc = nls.loadMessageBundle();
3535

3636
enum StepType {
3737
scp = 'scp',
38+
rsync = 'rsync',
3839
ssh = 'ssh',
3940
shell = 'shell',
4041
remoteShell = 'remoteShell',
@@ -1011,7 +1012,8 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
10111012
// Skip steps that doesn't match current launch mode. Explicit true/false check, since a step is always run when debug is undefined.
10121013
return true;
10131014
}
1014-
switch (step.type) {
1015+
const stepType: StepType = step.type;
1016+
switch (stepType) {
10151017
case StepType.command: {
10161018
// VS Code commands are the same regardless of which extension invokes them, so just invoke them here.
10171019
if (step.args && !Array.isArray(step.args)) {
@@ -1021,9 +1023,11 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
10211023
const returnCode: unknown = await vscode.commands.executeCommand(step.command, ...step.args);
10221024
return !returnCode;
10231025
}
1024-
case StepType.scp: {
1026+
case StepType.scp:
1027+
case StepType.rsync: {
1028+
const isScp: boolean = stepType === StepType.scp;
10251029
if (!step.files || !step.targetDir || !step.host) {
1026-
logger.getOutputChannelLogger().showErrorMessage(localize('missing.properties.scp', '"host", "files", and "targetDir" are required in scp steps.'));
1030+
logger.getOutputChannelLogger().showErrorMessage(localize('missing.properties.copyFile', '"host", "files", and "targetDir" are required in {0} steps.', isScp ? 'SCP' : 'rsync'));
10271031
return false;
10281032
}
10291033
const host: util.ISshHostInfo = { hostName: step.host.hostName, user: step.host.user, port: step.host.port };
@@ -1036,10 +1040,17 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
10361040
files = files.concat((await globAsync(fileGlob)).map(file => vscode.Uri.file(file)));
10371041
}
10381042
} else {
1039-
logger.getOutputChannelLogger().showErrorMessage(localize('incorrect.files.type.scp', '"files" must be a string or an array of strings in scp steps.'));
1043+
logger.getOutputChannelLogger().showErrorMessage(localize('incorrect.files.type.copyFile', '"files" must be a string or an array of strings in {0} steps.', isScp ? 'SCP' : 'rsync'));
10401044
return false;
10411045
}
1042-
const scpResult: util.ProcessReturnType = await scp(files, host, step.targetDir, config.scpPath, jumpHosts, cancellationToken);
1046+
1047+
let scpResult: util.ProcessReturnType;
1048+
if (isScp) {
1049+
scpResult = await scp(files, host, step.targetDir, config.scpPath, config.recursive, jumpHosts, cancellationToken);
1050+
} else {
1051+
scpResult = await rsync(files, host, step.targetDir, config.scpPath, config.recursive, jumpHosts, cancellationToken);
1052+
}
1053+
10431054
if (!scpResult.succeeded || cancellationToken?.isCancellationRequested) {
10441055
return false;
10451056
}

Extension/src/SSH/commands.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import { runSshTerminalCommandWithLogin } from './sshCommandRunner';
1212
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
1313
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
1414

15-
export async function scp(files: vscode.Uri[], host: ISshHostInfo, targetDir: string, scpPath?: string, jumpHosts?: ISshHostInfo[], cancellationToken?: vscode.CancellationToken): Promise<ProcessReturnType> {
15+
export async function scp(files: vscode.Uri[], host: ISshHostInfo, targetDir: string, recursive: boolean = true, scpPath?: string, jumpHosts?: ISshHostInfo[], cancellationToken?: vscode.CancellationToken): Promise<ProcessReturnType> {
1616
const args: string[] = [];
17+
if (recursive) {
18+
args.push('-r');
19+
}
1720
if (jumpHosts && jumpHosts.length > 0) {
1821
args.push('-J', jumpHosts.map(getFullHostAddress).join(','));
1922
}
@@ -26,6 +29,30 @@ export async function scp(files: vscode.Uri[], host: ISshHostInfo, targetDir: st
2629
return runSshTerminalCommandWithLogin(host, { systemInteractor: defaultSystemInteractor, nickname: 'scp', command: `"${scpPath || 'scp'}" ${args.join(' ')}`, token: cancellationToken });
2730
}
2831

32+
// Recursive is less important in rsync thank in SCP. SCP under recursive mode always follows symlinks, and potentially causes problems.
33+
// In rsync, there are options to avoid this issue (-l, -K). To mitigate confusion, we still provide a recursive option here like in SCP.
34+
export async function rsync(files: vscode.Uri[], host: ISshHostInfo, targetDir: string, recursive: boolean = true, rsyncPath?: string, jumpHosts?: ISshHostInfo[], cancellationToken?: vscode.CancellationToken): Promise<ProcessReturnType> {
35+
// --links, -l When symlinks are encountered, recreate the symlink on the destination.
36+
// --keep-dirlinks, -K Treat symlinked dir on receiver as dir.
37+
// --perms, -p Keep permissions.
38+
// --verbose, -v Verbose.
39+
// --compress, -z Compress file data during the transfer.
40+
const args: string[] = ['-lKpvz'];
41+
if (recursive) {
42+
args.push('-r');
43+
}
44+
if (jumpHosts && jumpHosts.length > 0) {
45+
args.push('-e', `ssh -J ${jumpHosts.map(getFullHostAddress).join(',')}`);
46+
}
47+
if (host.port) {
48+
// upper case P
49+
args.push(`--port=${host.port}`);
50+
}
51+
args.push(files.map(uri => `"${uri.fsPath}"`).join(' '), `${getFullHostAddressNoPort(host)}:${targetDir}`);
52+
53+
return runSshTerminalCommandWithLogin(host, { systemInteractor: defaultSystemInteractor, nickname: 'rsync', command: `"${rsyncPath || 'rsync'}" ${args.join(' ')}`, token: cancellationToken });
54+
}
55+
2956
export function ssh(host: ISshHostInfo, command: string, sshPath?: string, jumpHosts?: ISshHostInfo[], localForwards?: ISshLocalForwardInfo[], continueOn?: string, cancellationToken?: vscode.CancellationToken): Promise<ProcessReturnType> {
3057
const args: string[] = [];
3158
if (jumpHosts && jumpHosts.length > 0) {

0 commit comments

Comments
 (0)