Skip to content

Commit fb08f41

Browse files
Merge pull request #119 from DustinCampbell/add-launch-json-automagically
Add launch.json automagically
2 parents e4f579c + 1c2b569 commit fb08f41

File tree

5 files changed

+297
-96
lines changed

5 files changed

+297
-96
lines changed

src/assets.ts

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
'use strict';
7+
8+
import * as fs from 'fs-extra-promise';
9+
import * as path from 'path';
10+
import * as vscode from 'vscode';
11+
import * as tasks from 'vscode-tasks';
12+
import {OmnisharpServer} from './omnisharpServer';
13+
import * as serverUtils from './omnisharpUtils';
14+
import * as protocol from './protocol.ts'
15+
16+
interface DebugConfiguration {
17+
name: string,
18+
type: string,
19+
request: string,
20+
}
21+
22+
interface ConsoleLaunchConfiguration extends DebugConfiguration {
23+
preLaunchTask: string,
24+
program: string,
25+
args: string[],
26+
cwd: string,
27+
stopAtEntry: boolean
28+
}
29+
30+
interface CommandLine {
31+
command: string,
32+
args?: string
33+
}
34+
35+
interface LaunchBrowserConfiguration {
36+
enabled: boolean,
37+
args: string,
38+
windows?: CommandLine,
39+
osx: CommandLine,
40+
linux: CommandLine
41+
}
42+
43+
interface WebLaunchConfiguration extends ConsoleLaunchConfiguration {
44+
launchBrowser: LaunchBrowserConfiguration
45+
}
46+
47+
interface AttachConfiguration extends DebugConfiguration {
48+
processName: string
49+
}
50+
51+
interface Paths {
52+
vscodeFolder: string;
53+
tasksJsonPath: string;
54+
launchJsonPath: string;
55+
}
56+
57+
function getPaths(): Paths {
58+
const vscodeFolder = path.join(vscode.workspace.rootPath, '.vscode');
59+
60+
return {
61+
vscodeFolder: vscodeFolder,
62+
tasksJsonPath: path.join(vscodeFolder, 'tasks.json'),
63+
launchJsonPath: path.join(vscodeFolder, 'launch.json')
64+
}
65+
}
66+
67+
interface Operations {
68+
addTasksJson?: boolean,
69+
updateTasksJson?: boolean,
70+
addLaunchJson?: boolean
71+
}
72+
73+
function hasOperations(operations: Operations) {
74+
return operations.addLaunchJson ||
75+
operations.updateTasksJson ||
76+
operations.addLaunchJson;
77+
}
78+
79+
function getOperations() {
80+
const paths = getPaths();
81+
82+
return getBuildOperations(paths.tasksJsonPath).then(operations =>
83+
getLaunchOperations(paths.launchJsonPath, operations));
84+
}
85+
86+
function getBuildOperations(tasksJsonPath: string) {
87+
return new Promise<Operations>((resolve, reject) => {
88+
return fs.existsAsync(tasksJsonPath).then(exists => {
89+
if (exists) {
90+
fs.readFileAsync(tasksJsonPath).then(buffer => {
91+
const text = buffer.toString();
92+
const tasksJson: tasks.TaskConfiguration = JSON.parse(text);
93+
const buildTask = tasksJson.tasks.find(td => td.taskName === 'build');
94+
95+
resolve({ updateTasksJson: (buildTask === undefined) });
96+
});
97+
}
98+
else {
99+
resolve({ addTasksJson: true });
100+
}
101+
});
102+
});
103+
}
104+
105+
function getLaunchOperations(launchJsonPath: string, operations: Operations) {
106+
return new Promise<Operations>((resolve, reject) => {
107+
return fs.existsAsync(launchJsonPath).then(exists => {
108+
if (exists) {
109+
resolve(operations);
110+
}
111+
else {
112+
operations.addLaunchJson = true;
113+
resolve(operations);
114+
}
115+
});
116+
});
117+
}
118+
119+
function promptToAddAssets() {
120+
return new Promise<boolean>((resolve, reject) => {
121+
const item = { title: 'Yes' }
122+
123+
vscode.window.showInformationMessage('Required assets to build and debug are missing from your project. Add them?', item).then(selection => {
124+
return selection
125+
? resolve(true)
126+
: resolve(false);
127+
});
128+
});
129+
}
130+
131+
function createLaunchConfiguration(targetFramework: string, executableName: string): ConsoleLaunchConfiguration {
132+
return {
133+
name: '.NET Core Launch (console)',
134+
type: 'coreclr',
135+
request: 'launch',
136+
preLaunchTask: 'build',
137+
program: '${workspaceRoot}/bin/Debug/' + targetFramework + '/'+ executableName,
138+
args: [],
139+
cwd: '${workspaceRoot}',
140+
stopAtEntry: false
141+
}
142+
}
143+
144+
function createWebLaunchConfiguration(targetFramework: string, executableName: string): WebLaunchConfiguration {
145+
return {
146+
name: '.NET Core Launch (web)',
147+
type: 'coreclr',
148+
request: 'launch',
149+
preLaunchTask: 'build',
150+
program: '${workspaceRoot}/bin/Debug/' + targetFramework + '/'+ executableName,
151+
args: [],
152+
cwd: '${workspaceRoot}',
153+
stopAtEntry: false,
154+
launchBrowser: {
155+
enabled: true,
156+
args: '${auto-detect-url}',
157+
windows: {
158+
command: 'cmd.exe',
159+
args: '/C start ${auto-detect-url}'
160+
},
161+
osx: {
162+
command: 'open'
163+
},
164+
linux: {
165+
command: 'xdg-open'
166+
}
167+
}
168+
}
169+
}
170+
171+
function createAttachConfiguration(): AttachConfiguration {
172+
return {
173+
name: '.NET Core Attach',
174+
type: 'coreclr',
175+
request: 'attach',
176+
processName: '<example>'
177+
}
178+
}
179+
180+
function createLaunchJson(targetFramework: string, executableName: string): any {
181+
return {
182+
version: '0.2.0',
183+
configurations: [
184+
createLaunchConfiguration(targetFramework, executableName),
185+
createWebLaunchConfiguration(targetFramework, executableName),
186+
createAttachConfiguration()
187+
]
188+
}
189+
}
190+
191+
function createBuildTaskDescription(): tasks.TaskDescription {
192+
return {
193+
taskName: 'build',
194+
args: [],
195+
isBuildCommand: true,
196+
problemMatcher: '$msCompile'
197+
};
198+
}
199+
200+
function createTasksConfiguration(): tasks.TaskConfiguration {
201+
return {
202+
version: '0.1.0',
203+
command: 'dotnet',
204+
isShellCommand: true,
205+
args: [],
206+
tasks: [ createBuildTaskDescription() ]
207+
};
208+
}
209+
210+
function addTasksJsonIfNecessary(info: protocol.DotNetWorkspaceInformation, paths: Paths, operations: Operations) {
211+
return new Promise<void>((resolve, reject) => {
212+
if (!operations.addTasksJson) {
213+
return resolve();
214+
}
215+
216+
const tasksJson = createTasksConfiguration();
217+
const tasksJsonText = JSON.stringify(tasksJson, null, ' ');
218+
219+
return fs.writeFileAsync(paths.tasksJsonPath, tasksJsonText);
220+
});
221+
}
222+
223+
function addLaunchJsonIfNecessary(info: protocol.DotNetWorkspaceInformation, paths: Paths, operations: Operations) {
224+
return new Promise<void>((resolve, reject) => {
225+
if (!operations.addLaunchJson) {
226+
return resolve();
227+
}
228+
229+
let targetFramework = '<target-framework>';
230+
let executableName = '<project-name.dll>';
231+
232+
let projectWithEntryPoint = info.Projects.find(project => project.EmitEntryPoint === true);
233+
234+
if (projectWithEntryPoint) {
235+
targetFramework = projectWithEntryPoint.TargetFramework.ShortName;
236+
executableName = path.basename(projectWithEntryPoint.CompilationOutputAssemblyFile);
237+
}
238+
239+
const launchJson = createLaunchJson(targetFramework, executableName);
240+
const launchJsonText = JSON.stringify(launchJson, null, ' ');
241+
242+
return fs.writeFileAsync(paths.launchJsonPath, launchJsonText);
243+
});
244+
}
245+
246+
export function addAssetsIfNecessary(server: OmnisharpServer) {
247+
if (!vscode.workspace.rootPath) {
248+
return;
249+
}
250+
251+
// If there is no project.json, we won't bother to prompt the user for tasks.json.
252+
const projectJsonPath = path.join(vscode.workspace.rootPath, 'project.json');
253+
if (!fs.existsSync(projectJsonPath)) {
254+
return;
255+
}
256+
257+
return serverUtils.requestWorkspaceInformation(server).then(info => {
258+
// If there are no .NET Core projects, we won't bother offering to add assets.
259+
if ('DotNet' in info && info.DotNet.Projects.length > 0) {
260+
return getOperations().then(operations => {
261+
if (!hasOperations(operations)) {
262+
return;
263+
}
264+
265+
promptToAddAssets().then(addAssets => {
266+
if (!addAssets) {
267+
return;
268+
}
269+
270+
const paths = getPaths();
271+
272+
return fs.ensureDirAsync(paths.vscodeFolder).then(() => {
273+
return Promise.all([
274+
addTasksJsonIfNecessary(info.DotNet, paths, operations),
275+
addLaunchJsonIfNecessary(info.DotNet, paths, operations)
276+
]);
277+
});
278+
});
279+
});
280+
}
281+
});
282+
}

src/omnisharpMain.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {StdioOmnisharpServer} from './omnisharpServer';
2323
import forwardChanges from './features/changeForwarding';
2424
import reportStatus from './features/omnisharpStatus';
2525
import {installCoreClrDebug} from './coreclr-debug';
26-
import {promptToAddBuildTaskIfNecessary} from './tasks';
26+
import {addAssetsIfNecessary} from './assets';
2727
import * as vscode from 'vscode';
2828
import TelemetryReporter from 'vscode-extension-telemetry';
2929

@@ -74,6 +74,11 @@ export function activate(context: vscode.ExtensionContext): any {
7474

7575
disposables.push(registerCommands(server, context.extensionPath));
7676
disposables.push(reportStatus(server));
77+
78+
disposables.push(server.onServerStart(() => {
79+
// Update or add tasks.json and launch.json
80+
addAssetsIfNecessary(server);
81+
}));
7782

7883
// read and store last solution or folder path
7984
disposables.push(server.onBeforeServerStart(path => context.workspaceState.update('lastSolutionPathOrFolder', path)));
@@ -86,9 +91,6 @@ export function activate(context: vscode.ExtensionContext): any {
8691
server.stop();
8792
}));
8893

89-
// Check to see if there is a tasks.json with a "build" task and prompt the user to add it if missing.
90-
promptToAddBuildTaskIfNecessary();
91-
9294
// install coreclr-debug
9395
installCoreClrDebug(context, reporter);
9496

src/omnisharpServer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ export abstract class OmnisharpServer {
268268
this._serverProcess = value.process;
269269
this._requestDelays = {};
270270
this._fireEvent(Events.StdOut, `[INFO] Started OmniSharp from '${value.command}' with process id ${value.process.pid}...\n`);
271-
this._fireEvent(Events.ServerStart, solutionPath);
272271
this._setState(ServerState.Started);
272+
this._fireEvent(Events.ServerStart, solutionPath);
273273
return this._doConnect();
274274
}).then(_ => {
275275
this._processQueue();

src/protocol.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,12 +268,20 @@ export interface DotNetWorkspaceInformation {
268268
export interface DotNetProject {
269269
Path: string;
270270
Name: string;
271+
TargetFramework: DotNetFramework;
271272
CompilationOutputPath: string;
272273
CompilationOutputAssemblyFile: string;
273274
CompilationOutputPdbFile: string;
275+
EmitEntryPoint?: boolean;
274276
SourceFiles: string[];
275277
}
276278

279+
export interface DotNetFramework {
280+
Name: string;
281+
FriendlyName: string;
282+
ShortName: string;
283+
}
284+
277285
export interface RenameRequest extends Request {
278286
RenameTo: string;
279287
WantsTextChanges?: boolean;

0 commit comments

Comments
 (0)