Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for C# #498

Merged
merged 8 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ enter testcases.

- C++
- C
- C#
- Rust
- Go
- Haskell
Expand Down
26 changes: 24 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,27 @@
],
"description": "C++ compiler's argument that specifies the output files."
},
"cph.language.csharp.Args": {
"title": "Compilation flags for .cs files",
"type": "string",
"default": "",
"description": "Space seperated additional flags passed to dotnet while compiling your file."
},
"cph.language.csharp.SubmissionCompiler": {
"type": "string",
"default": "C# 8, .NET Core 3.1",
"enum": [
"C# 8, .NET Core 3.1",
"C# 10, .NET SDK 6.0",
"C# Mono 6.8"
],
"description": "The compiler chosen in the drop down during Codeforces submission for C#"
},
"cph.language.csharp.Command": {
"type": "string",
"default": "dotnet",
"description": "Command used to compile .cs files. 'dotnet' for C# .NET, 'mcs' for C# Mono"
},
"cph.language.python.Args": {
"title": "Compilation flags for Python",
"type": "string",
Expand Down Expand Up @@ -305,13 +326,14 @@
"rust",
"js",
"java",
"ruby"
"ruby",
"csharp"
],
"description": "The default language for problems imported via Competitive Companion (None will give option to select language on importing problem every time)"
},
"cph.general.menuChoices": {
"type": "string",
"default": "cpp java js python c rust ruby",
"default": "cpp java js python c rust ruby csharp",
"description": "Space separated languages, in top to bottom order, shown in menu when a problem is imported via Competitive Companion. Example 'java python' would show java on top, followed by python."
},
"cph.general.useShortCodeForcesName": {
Expand Down
193 changes: 189 additions & 4 deletions src/compiler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getLanguage, ocHide, ocShow, ocWrite } from './utils';
import { Language } from './types';
import { spawn } from 'child_process';
import { spawn, SpawnOptionsWithoutStdio } from 'child_process';
import { platform } from 'os';
import path from 'path';
import {
getCOutputArgPref,
Expand All @@ -19,7 +20,7 @@ export const setOnlineJudgeEnv = (value: boolean) => {

/**
* Get the location to save the generated binary in. If save location is
* available in preferences, returns that, otherwise returns the director of
* available in preferences, returns that, otherwise returns the directory of
* active file.
*
* If it is a interpteted language, simply returns the path to the source code.
Expand All @@ -31,7 +32,20 @@ export const getBinSaveLocation = (srcPath: string): string => {
if (language.skipCompile) {
return srcPath;
}
const ext = language.name == 'java' ? '*.class' : '.bin';
let ext: string;
switch (language.name) {
case 'java': {
ext = '*.class';
break;
}
case 'csharp': {
ext = language.compiler.includes('dotnet') ? '_bin' : '.bin';
break;
}
default: {
ext = '.bin';
}
}
const savePreference = getSaveLocationPref();
const srcFileName = path.parse(srcPath).name;
const binFileName = srcFileName + ext;
Expand Down Expand Up @@ -117,6 +131,41 @@ const getFlags = (language: Language, srcPath: string): string[] => {
];
break;
}
case 'csharp': {
const projDir = getDotnetProjectLocation(language, srcPath);
const binPath = getBinSaveLocation(srcPath);

if (language.compiler.includes('dotnet')) {
ret = [
'build',
projDir,
'-c',
'Release',
'-o',
binPath,
'--force',
...args,
];
if (onlineJudgeEnv) {
ret.push('/p:DefineConstants="TRACE;ONLINE_JUDGE"');
}
} else {
// mcs will run on shell, need to wrap paths with quotes.
// otherwise it will raise error when the paths contain whitespace.
let wrpSrcPath = srcPath;
let wrpBinPath = binPath;
if (platform() === 'win32') {
wrpSrcPath = '"' + srcPath + '"';
wrpBinPath = '"' + binPath + '"';
}

ret = [wrpSrcPath, '-out:' + wrpBinPath, ...args];
if (onlineJudgeEnv) {
ret.push('-define:ONLINE_JUDGE');
}
}
break;
}
default: {
ret = [];
break;
Expand All @@ -125,6 +174,115 @@ const getFlags = (language: Language, srcPath: string): string[] => {
return ret;
};

/**
* Get the path of the .NET project file (*.csproj) from the location of the source code.
* If the compiler is not dotnet, simply returns the path to the source code.
*
* @param language The Language object for the source code
* @returns location of the .NET project file (*.csproj)
*/
const getDotnetProjectLocation = (
language: Language,
srcPath: string,
): string => {
if (!language.compiler.includes('dotnet')) {
return srcPath;
}

const projName = '.cphcsrun';
const srcDir = path.dirname(srcPath);
const projDir = path.join(srcDir, projName);
return path.join(projDir, projName + '.csproj');
};

/**
* Create a new .NET project to compile the source code.
* It would be created under the same directory as the code.
*
* @param language The Language object for the source code
* @param srcPath location of the source code
*/
const createDotnetProject = async (
language: Language,
srcPath: string,
): Promise<boolean> => {
const result = new Promise<boolean>((resolve) => {
const projDir = path.dirname(
getDotnetProjectLocation(language, srcPath),
);

console.log('Creating new .NET project');
const args = ['new', 'console', '--force', '-o', projDir];
const newProj = spawn(language.compiler, args);

let error = '';

newProj.stderr.on('data', (data) => {
error += data;
});

newProj.on('exit', (exitcode) => {
const exitCode = exitcode || 0;
const hideWarningsWhenCompiledOK = getHideStderrorWhenCompiledOK();

if (exitCode !== 0) {
ocWrite(
`Exit code: ${exitCode} Errors while creating new .NET project:\n` +
error,
);
ocShow();
resolve(false);
return;
}

if (!hideWarningsWhenCompiledOK && error.trim() !== '') {
ocWrite(
`Exit code: ${exitCode} Warnings while creating new .NET project:\n ` +
error,
);
ocShow();
}

const destPath = path.join(projDir, 'Program.cs');
console.log(
'Copying source code to the project',
srcPath,
destPath,
);
try {
const isLinux = platform() == 'linux';

if (isLinux) {
spawn('cp', ['-f', srcPath, destPath]);
} else {
const wrpSrcPath = '"' + srcPath + '"';
const wrpDestPath = '"' + destPath + '"';
spawn(
'cmd.exe',
['/c', 'copy', '/y', wrpSrcPath, wrpDestPath],
{ windowsVerbatimArguments: true },
);
}
resolve(true);
} catch (err) {
console.error('Error while copying source code', err);
ocWrite('Errors while creating new .NET project:\n' + err);
ocShow();
resolve(false);
}
});

newProj.on('error', (err) => {
console.log(err);
ocWrite('Errors while creating new .NET project:\n' + err);
ocShow();
resolve(false);
});
});

return result;
};

/**
* Compile a source file, storing the output binary in a location based on user
* preferences. If `skipCompile` is true for a language, skips the compilation
Expand All @@ -143,6 +301,33 @@ export const compileFile = async (srcPath: string): Promise<boolean> => {
if (language.skipCompile) {
return Promise.resolve(true);
}

const spawnOpts: SpawnOptionsWithoutStdio = {
cwd: undefined,
env: process.env,
};

if (language.name === 'csharp') {
if (language.compiler.includes('dotnet')) {
const projResult = await createDotnetProject(language, srcPath);
if (!projResult) {
getJudgeViewProvider().extensionToJudgeViewMessage({
command: 'compiling-stop',
});
getJudgeViewProvider().extensionToJudgeViewMessage({
command: 'not-running',
});
return Promise.resolve(false);
}
} else {
// HACK: Mono only provides mcs.bat for Windows?
// spawn cannot run mcs.bat :(
if (platform() === 'win32') {
spawnOpts.shell = true;
}
}
}

getJudgeViewProvider().extensionToJudgeViewMessage({
command: 'compiling-start',
});
Expand All @@ -151,7 +336,7 @@ export const compileFile = async (srcPath: string): Promise<boolean> => {
const result = new Promise<boolean>((resolve) => {
let compiler;
try {
compiler = spawn(language.compiler, flags);
compiler = spawn(language.compiler, flags, spawnOpts);
} catch (err) {
vscode.window.showErrorMessage(
`Could not launch the compiler ${language.compiler}. Is it installed?`,
Expand Down
6 changes: 6 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default {
extensions: {
c: 'c',
cpp: 'cpp',
csharp: 'cs',
python: 'py',
ruby: 'rb',
rust: 'rs',
Expand All @@ -24,6 +25,7 @@ export default {
compilers: {
c: 'gcc',
cpp: 'g++',
csharp: 'dotnet',
python: 'python',
ruby: 'ruby',
rust: 'rustc',
Expand All @@ -42,6 +44,9 @@ export default {
'Microsoft Visual C++ 2017': 59,
'Microsoft Visual C++ 2010': 2,
'Clang++17 Diagnostics': 52,
'C# 8, .NET Core 3.1': 65,
'C# 10, .NET SDK 6.0': 79,
'C# Mono 6.8': 9,
'Java 11.0.6': 60,
'Java 1.8.0_241': 36,
'Node.js 15.8.0 (64bit)': 55,
Expand All @@ -66,6 +71,7 @@ export default {
'go',
'hs',
'rb',
'cs',
],
skipCompile: ['py', 'js', 'rb'],
};
43 changes: 40 additions & 3 deletions src/executions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,27 @@ export const runTestCase = (
process = spawn('java', args);
break;
}
case 'csharp': {
let binFileName: string;

if (language.compiler.includes('dotnet')) {
const projName = '.cphcsrun';
const isLinux = platform() == 'linux';
if (isLinux) {
binFileName = projName;
} else {
binFileName = projName + '.exe';
}

const binFilePath = path.join(binPath, binFileName);
process = spawn(binFilePath, ['/stack:67108864'], spawnOpts);
} else {
// Run with mono
process = spawn('mono', [binPath], spawnOpts);
}

break;
}
default: {
process = spawn(binPath, spawnOpts);
}
Expand Down Expand Up @@ -157,10 +178,26 @@ export const deleteBinary = (language: Language, binPath: string) => {
}
console.log('Deleting binary', binPath);
try {
if (platform() == 'linux') {
spawn('rm', [binPath]);
const isLinux = platform() == 'linux';
const isFile = path.extname(binPath);

if (isLinux) {
if (isFile) {
spawn('rm', [binPath]);
} else {
spawn('rm', ['-r', binPath]);
}
} else {
spawn('del', [binPath], { shell: true });
const nrmBinPath = '"' + binPath + '"';
if (isFile) {
spawn('cmd.exe', ['/c', 'del', nrmBinPath], {
windowsVerbatimArguments: true,
});
} else {
spawn('cmd.exe', ['/c', 'rd', '/s', '/q', nrmBinPath], {
windowsVerbatimArguments: true,
});
}
}
} catch (err) {
console.error('Error while deleting binary', err);
Expand Down
Loading
Loading