diff --git a/src/Backend/CompilerImpl/DebianCompiler.ts b/src/Backend/CompilerImpl/DebianCompiler.ts new file mode 100644 index 000000000..8fc5f95d2 --- /dev/null +++ b/src/Backend/CompilerImpl/DebianCompiler.ts @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as cp from "child_process"; +import * as vscode from "vscode"; + +import { pipedSpawnSync } from "../../Utils/PipedSpawnSync"; +import { Command } from "../Command"; +import { Compiler } from "../Compiler"; +import { PackageInfo, ToolchainInfo, Toolchains } from "../Toolchain"; +import { + DebianArch, + DebianRepo, + DebianToolchain, +} from "../ToolchainImpl/DebianToolchain"; +import { Version } from "../Version"; + +export class DebianCompiler implements Compiler { + private readonly toolchainTypes: string[]; + private readonly toolchainName: string; + + private debianToolchainClass: typeof DebianToolchain; + private depends: Array; + private prerequisites: string; + + private readonly debianRepo: DebianRepo | undefined; + private readonly debianArch: DebianArch | undefined; + + constructor({ + toolchainTypes, + toolchainName, + debianToolchainClass, + depends, + prerequisites, + debianRepo, + debianArch, + }: { + toolchainTypes: string[]; + toolchainName: string; + debianToolchainClass: typeof DebianToolchain; + depends: Array; + prerequisites: string; + debianRepo?: DebianRepo; + debianArch?: DebianArch; + }) { + this.toolchainTypes = toolchainTypes; + this.toolchainName = toolchainName; + + this.debianToolchainClass = debianToolchainClass; + this.depends = depends; + this.prerequisites = prerequisites; + + this.debianRepo = debianRepo; + this.debianArch = debianArch; + } + + getToolchainTypes(): string[] { + return this.toolchainTypes; + } + + parseVersion(version: string): Version { + if (!version.trim()) { + throw Error("Invalid version format."); + } + + let _version = version; + let option = ""; + + const optionIndex = version.search(/[~+-]/); + if (optionIndex !== -1) { + option = version.slice(optionIndex); + _version = version.slice(0, optionIndex); + } + + const splitedVersion = _version.split("."); + + if (splitedVersion.length > 3) { + throw Error("Invalid version format."); + } + + let major: number | string; + let minor: number | string; + let patch: number | string; + + [major = "0", minor = "0", patch = "0"] = _version.split("."); + + const epochIndex = major.search(/:/); + if (epochIndex !== -1) { + major = major.slice(epochIndex + 1); + } + + major = Number(major); + minor = Number(minor); + patch = Number(patch); + + if (isNaN(major) || isNaN(minor) || isNaN(patch)) { + throw Error("Invalid version format."); + } + return new Version(major, minor, patch, option); + } + + getToolchains( + _toolchainType: string, + _start: number, + _count: number + ): Toolchains { + if (_toolchainType !== "latest") { + throw Error(`Invalid toolchain type : ${_toolchainType}`); + } + + if (_start < 0) { + throw Error(`wrong start number: ${_start}`); + } + if (_count < 0) { + throw Error(`wrong count number: ${_count}`); + } + if (_count === 0) { + return []; + } + + try { + cp.spawnSync(`apt-cache show ${this.toolchainName}`); + } catch (error) { + throw Error(`Getting ${this.toolchainName} package list is failed`); + } + + let result; + try { + result = pipedSpawnSync( + "apt-cache", + ["madison", `${this.toolchainName}`], + { encoding: "utf8" }, + "awk", + ['{printf $3" "}'], + { encoding: "utf8" } + ); + } catch (error) { + throw Error( + `Getting ${this.toolchainName} package version list is failed` + ); + } + + if (result.status !== 0) { + return []; + } + + const toolchainVersions: string = result.stdout.toString(); + const versionList = toolchainVersions.trim().split(" "); + + const availableToolchains = new Toolchains(); + for (const version of versionList) { + const toolchainInfo = new ToolchainInfo( + this.toolchainName, + "Description: test", + this.parseVersion(version) + ); + + const toolchain = new this.debianToolchainClass( + toolchainInfo, + this.debianRepo, + this.debianArch + ); + availableToolchains.push(toolchain); + } + + return availableToolchains; + } + + getInstalledToolchains(_toolchainType: string): Toolchains { + if (_toolchainType !== "latest") { + throw Error(`Invalid toolchain type : ${_toolchainType}`); + } + + let result; + try { + result = cp.spawnSync( + "dpkg-query", + [ + "--show", + `--showformat='\${Version} \${Description}'`, + `${this.toolchainName}`, + ], + { encoding: "utf8" } + ); + } catch (error) { + throw new Error( + `Getting installed ${this.toolchainName} package list is failed` + ); + } + + if (result.status !== 0) { + return []; + } + + // NOTE + // The output format string of dpkg-query is '${Version} ${Description}'. + // To remove the first and last single quote character of output string, it slices from 1 to -1. + const installedToolchain: string = result.stdout.toString().slice(1, -1); + + const descriptionIdx = installedToolchain.search(" "); + const versionStr = installedToolchain.slice(0, descriptionIdx).trim(); + const description = installedToolchain.slice(descriptionIdx).trim(); + + // NOTE + // onecc-docker does not have any explict dependencies, + // but it has internally dependency to one-compiler and it is seen in depends. + + // TODO + // onecc-docker's depends should be modified later so that we can read the one-compiler version. + + const toolchainInfo = new ToolchainInfo( + this.toolchainName, + description, + this.parseVersion(versionStr), + this.depends + ); + const toolchain = new this.debianToolchainClass( + toolchainInfo, + this.debianRepo, + this.debianArch + ); + return [toolchain]; + } + + prerequisitesForGetToolchains(): Command { + const extensionId = "Samsung.one-vscode"; + const ext = vscode.extensions.getExtension( + extensionId + ) as vscode.Extension; + const scriptPath = vscode.Uri.joinPath( + ext!.extensionUri, + "script", + this.prerequisites + ).fsPath; + + const cmd = new Command("/bin/sh", [`${scriptPath}`]); + cmd.setRoot(); + return cmd; + } +} diff --git a/src/Backend/EdgeTPU/EdgeTPUToolchain.ts b/src/Backend/EdgeTPU/EdgeTPUToolchain.ts index f71cc7f5c..1c7f95ef9 100644 --- a/src/Backend/EdgeTPU/EdgeTPUToolchain.ts +++ b/src/Backend/EdgeTPU/EdgeTPUToolchain.ts @@ -14,13 +14,16 @@ * limitations under the License. */ -import { Command } from "../Command"; -import { DebianToolchain } from "../ToolchainImpl/DebianToolchain"; - import * as ini from "ini"; import * as fs from "fs"; import * as path from "path"; + import { Logger } from "../../Utils/Logger"; +import { Command } from "../Command"; +import { PackageInfo } from "../Toolchain"; +import { DebianToolchain } from "../ToolchainImpl/DebianToolchain"; +import { Version } from "../Version"; +import { DebianCompiler } from "../CompilerImpl/DebianCompiler"; class EdgeTPUDebianToolchain extends DebianToolchain { run(cfg: string): Command { @@ -77,4 +80,64 @@ class EdgeTPUDebianToolchain extends DebianToolchain { } } -export { EdgeTPUDebianToolchain }; +class EdgeTPUCompiler extends DebianCompiler { + constructor() { + super({ + toolchainTypes: ["latest"], + toolchainName: "edgetpu-compiler", + debianToolchainClass: EdgeTPUDebianToolchain, + depends: [ + new PackageInfo("edgetpu_compiler", new Version(16, 0, undefined)), + ], + prerequisites: "prerequisitesForGetEdgeTPUToolchain.sh", + }); + } + + parseVersion(version: string): Version { + if (!version.trim()) { + throw Error("Invalid version format."); + } + + let _version = version; + let option = ""; + + const optionIndex = version.search(/[~+-]/); + if (optionIndex !== -1) { + option = version.slice(optionIndex); + _version = version.slice(0, optionIndex); + } + + const splitedVersion = _version.split("."); + + if (splitedVersion.length > 3) { + throw Error("Invalid version format."); + } + + let major: number | string; + let minor: number | string; + let patch: number | string; + + [major = "0", minor = "0", patch = "0"] = _version.split("."); + + const epochIndex = major.search(/:/); + if (epochIndex !== -1) { + major = major.slice(epochIndex + 1); + } + + major = Number(major); + minor = Number(minor); + patch = Number(patch); + + if (isNaN(major) || isNaN(minor) || isNaN(patch)) { + throw Error("Invalid version format."); + } + + if (splitedVersion.length === 2 && !option) { + return new Version(major, minor, undefined); + } + + return new Version(major, minor, patch, option); + } +} + +export { EdgeTPUDebianToolchain, EdgeTPUCompiler }; diff --git a/src/Backend/One/OneToolchain.ts b/src/Backend/One/OneToolchain.ts index 379b8d71e..b3139277f 100644 --- a/src/Backend/One/OneToolchain.ts +++ b/src/Backend/One/OneToolchain.ts @@ -14,22 +14,21 @@ * limitations under the License. */ -import * as cp from "child_process"; import * as vscode from "vscode"; -import { pipedSpawnSync } from "../../Utils/PipedSpawnSync"; import { Balloon } from "../../Utils/Balloon"; import { Backend } from "../Backend"; import { Command } from "../Command"; import { Compiler } from "../Compiler"; import { Executor } from "../Executor"; -import { PackageInfo, ToolchainInfo, Toolchains } from "../Toolchain"; +import { PackageInfo } from "../Toolchain"; import { DebianArch, DebianRepo, DebianToolchain, } from "../ToolchainImpl/DebianToolchain"; import { Version } from "../Version"; +import { DebianCompiler } from "../CompilerImpl/DebianCompiler"; class OneDebianToolchain extends DebianToolchain { run(cfg: string): Command { @@ -50,209 +49,21 @@ class OneDebianToolchain extends DebianToolchain { } } -class OneCompiler implements Compiler { - private readonly toolchainTypes: string[]; - private readonly toolchainName: string; - private readonly debianRepo: DebianRepo; - private readonly debianArch: DebianArch; - +class OneCompiler extends DebianCompiler { constructor() { - this.toolchainName = "onecc-docker"; - this.toolchainTypes = ["latest"]; - - this.debianRepo = new DebianRepo( - "http://ppa.launchpad.net/one-compiler/onecc-docker/ubuntu", - "bionic", - "main" - ); - this.debianArch = DebianArch.amd64; - } - - getToolchainTypes(): string[] { - return this.toolchainTypes; - } - - parseVersion(version: string): Version { - if (!version.trim()) { - throw Error("Invalid version format."); - } - - let _version = version; - let option = ""; - - const optionIndex = version.search(/[~+-]/); - if (optionIndex !== -1) { - option = version.slice(optionIndex); - _version = version.slice(0, optionIndex); - } - - const splitedVersion = _version.split("."); - - if (splitedVersion.length > 3) { - throw Error("Invalid version format."); - } - - let major: number | string; - let minor: number | string; - let patch: number | string; - - [major = "0", minor = "0", patch = "0"] = _version.split("."); - - const epochIndex = major.search(/:/); - if (epochIndex !== -1) { - major = major.slice(epochIndex + 1); - } - - major = Number(major); - minor = Number(minor); - patch = Number(patch); - - if (isNaN(major) || isNaN(minor) || isNaN(patch)) { - throw Error("Invalid version format."); - } - return new Version(major, minor, patch, option); - } - - getToolchains( - _toolchainType: string, - _start: number, - _count: number - ): Toolchains { - if (_toolchainType !== "latest") { - throw Error(`Invalid toolchain type : ${_toolchainType}`); - } - - if (_start < 0) { - throw Error(`wrong start number: ${_start}`); - } - if (_count < 0) { - throw Error(`wrong count number: ${_count}`); - } - if (_count === 0) { - return []; - } - - try { - cp.spawnSync(`apt-cache show ${this.toolchainName}`); - } catch (error) { - throw Error(`Getting ${this.toolchainName} package list is failed`); - } - - let result; - try { - result = pipedSpawnSync( - "apt-cache", - ["madison", `${this.toolchainName}`], - { encoding: "utf8" }, - "awk", - ['{printf $3" "}'], - { encoding: "utf8" } - ); - } catch (error) { - throw Error( - `Getting ${this.toolchainName} package version list is failed` - ); - } - - if (result.status !== 0) { - return []; - } - - const toolchainVersions: string = result.stdout.toString(); - const versionList = toolchainVersions.trim().split(" "); - - const availableToolchains = new Toolchains(); - for (const version of versionList) { - const toolchainInfo = new ToolchainInfo( - this.toolchainName, - "Description: test", - this.parseVersion(version) - ); - - const toolchain = new OneDebianToolchain( - toolchainInfo, - this.debianRepo, - this.debianArch - ); - availableToolchains.push(toolchain); - } - - return availableToolchains; - } - - getInstalledToolchains(_toolchainType: string): Toolchains { - if (_toolchainType !== "latest") { - throw Error(`Invalid toolchain type : ${_toolchainType}`); - } - - let result; - try { - result = cp.spawnSync( - "dpkg-query", - [ - "--show", - `--showformat='\${Version} \${Description}'`, - `${this.toolchainName}`, - ], - { encoding: "utf8" } - ); - } catch (error) { - throw new Error( - `Getting installed ${this.toolchainName} package list is failed` - ); - } - - if (result.status !== 0) { - return []; - } - - // NOTE - // The output format string of dpkg-query is '${Version} ${Description}'. - // To remove the first and last single quote character of output string, it slices from 1 to -1. - const installedToolchain: string = result.stdout.toString().slice(1, -1); - - const descriptionIdx = installedToolchain.search(" "); - const versionStr = installedToolchain.slice(0, descriptionIdx).trim(); - const description = installedToolchain.slice(descriptionIdx).trim(); - - // NOTE - // onecc-docker does not have any explict dependencies, - // but it has internally dependency to one-compiler and it is seen in depends. - - // TODO - // onecc-docker's depends should be modified later so that we can read the one-compiler version. - - const depends: Array = [ - new PackageInfo("one-compiler", new Version(1, 21, 0)), - ]; - const toolchainInfo = new ToolchainInfo( - this.toolchainName, - description, - this.parseVersion(versionStr), - depends - ); - const toolchain = new OneDebianToolchain( - toolchainInfo, - this.debianRepo, - this.debianArch - ); - return [toolchain]; - } - - prerequisitesForGetToolchains(): Command { - const extensionId = "Samsung.one-vscode"; - const ext = vscode.extensions.getExtension( - extensionId - ) as vscode.Extension; - const scriptPath = vscode.Uri.joinPath( - ext!.extensionUri, - "script", - "prerequisitesForGetOneToolchain.sh" - ).fsPath; - - const cmd = new Command("/bin/sh", [`${scriptPath}`]); - cmd.setRoot(); - return cmd; + super({ + toolchainTypes: ["latest"], + toolchainName: "onecc-docker", + debianToolchainClass: OneDebianToolchain, + depends: [new PackageInfo("one-compiler", new Version(1, 21, 0))], + prerequisites: "prerequisitesForGetOneToolchain.sh", + debianRepo: new DebianRepo( + "http://ppa.launchpad.net/one-compiler/onecc-docker/ubuntu", + "bionic", + "main" + ), + debianArch: DebianArch.amd64, + }); } } diff --git a/src/Tests/Backend/EdgeTPU/EdgeTPUToolchain.test.ts b/src/Tests/Backend/EdgeTPU/EdgeTPUToolchain.test.ts index 757863a15..99ab9fdac 100644 --- a/src/Tests/Backend/EdgeTPU/EdgeTPUToolchain.test.ts +++ b/src/Tests/Backend/EdgeTPU/EdgeTPUToolchain.test.ts @@ -15,8 +15,12 @@ */ import { assert } from "chai"; +import * as vscode from "vscode"; -import { EdgeTPUDebianToolchain } from "../../../Backend/EdgeTPU/EdgeTPUToolchain"; +import { + EdgeTPUCompiler, + EdgeTPUDebianToolchain, +} from "../../../Backend/EdgeTPU/EdgeTPUToolchain"; import { ToolchainInfo } from "../../../Backend/Toolchain"; import { Version } from "../../../Backend/Version"; import { TestBuilder } from "../../TestBuilder"; @@ -117,3 +121,150 @@ suite("Backend", function () { }); }); }); + +suite("EdgeTPUCompiler", function () { + suite("#constructor()", function () { + test("Create EdgeTPUToolchain compiler", function (pass) { + assert.doesNotThrow(() => new EdgeTPUCompiler()); + + pass(); + assert.ok(true); + }); + }); + + suite("#getToolchainTypes", function () { + test("returns EdgeTPUCompiler's type", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + const toolchainTypes = edgeTPUCompiler.getToolchainTypes(); + assert.deepEqual(toolchainTypes, ["latest"]); + }); + }); + + suite("#parseVersion", function () { + test("returns Version object from string version", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + const version1 = edgeTPUCompiler.parseVersion("1.0.2~RC0"); + const version2 = new Version(1, 0, 2, "~RC0"); + assert.deepEqual(version1, version2); + }); + test("returns Version object from string version without patch and option", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + const version1 = edgeTPUCompiler.parseVersion("16.0"); + const version2 = new Version(16, 0, undefined); + assert.deepEqual(version1, version2); + }); + test("returns Version object from string version without option", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + const version1 = edgeTPUCompiler.parseVersion("2.1.302470888"); + const version2 = new Version(2, 1, 302470888); + assert.deepEqual(version1, version2); + }); + test("returns Version object from string version without patch", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + const version1 = edgeTPUCompiler.parseVersion("1.0-beta"); + const version2 = new Version(1, 0, 0, "-beta"); + assert.deepEqual(version1, version2); + }); + test("NEG: check invalid version format without numbers", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + assert.throws(() => edgeTPUCompiler.parseVersion("a.b.c")); + }); + test("NEG: check invalid version format with too many numbers", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + assert.throws(() => edgeTPUCompiler.parseVersion("1.2.3.4")); + }); + test("NEG: check invalid version format with empty string", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + assert.throws(() => edgeTPUCompiler.parseVersion("")); + }); + }); + + suite("#getToolchains", function () { + test("get toolchain list", function () { + // No positive test for get toolchain list + // To test this test case, prerequisites() function should be run first. + // However this function needs root permission so it couldn't be executed. + const edgeTPUCompiler = new EdgeTPUCompiler(); + assert.isDefined(edgeTPUCompiler.getToolchainTypes); + }); + + test("return empty toolchain array when count is 0", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + const toolchainType = "latest"; + const start = 0; + const count = 0; + assert.deepStrictEqual( + edgeTPUCompiler.getToolchains(toolchainType, start, count), + [] + ); + }); + + test("NEG: request wrong toolchain type", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + const dummyToolchainType = "dummy"; + const start = 0; + const count = 1; + assert.throws(() => + edgeTPUCompiler.getToolchains(dummyToolchainType, start, count) + ); + }); + + test("NEG: request wrong start number", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + const toolchainType = "latest"; + const start = -1; + const count = 1; + assert.throws(() => + edgeTPUCompiler.getToolchains(toolchainType, start, count) + ); + }); + + test("NEG: request wrong count number", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + const toolchainType = "latest"; + const start = 0; + const count = -1; + assert.throws(() => + edgeTPUCompiler.getToolchains(toolchainType, start, count) + ); + }); + }); + + suite("#getInstalledToolchains", function () { + test("get toolchain list", function () { + // No positive test for get toolchain list + // To test this test case, prerequisites() function should be run first. + // However this function needs root permission so it couldn't be executed. + const edgeTPUCompiler = new EdgeTPUCompiler(); + assert.isDefined(edgeTPUCompiler.getInstalledToolchains); + }); + + test("NEG: request wrong toolchain type", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + const dummyToolchainType = "dummy"; + assert.throws(() => + edgeTPUCompiler.getInstalledToolchains(dummyToolchainType) + ); + }); + }); + + suite("#prerequisitesForGetToolchains", function () { + test("returns a command which executes a shell script for prerequisites", function () { + const edgeTPUCompiler = new EdgeTPUCompiler(); + const extensionId = "Samsung.one-vscode"; + const ext = vscode.extensions.getExtension( + extensionId + ) as vscode.Extension; + const scriptPath = vscode.Uri.joinPath( + ext!.extensionUri, + "script", + "prerequisitesForGetEdgeTPUToolchain.sh" + ).fsPath; + const cmd = `sudo /bin/sh ${scriptPath}`; + assert.deepStrictEqual( + edgeTPUCompiler.prerequisitesForGetToolchains().str(), + cmd + ); + }); + }); +});