diff --git a/README.md b/README.md index 66d3c48d..1c42d600 100644 --- a/README.md +++ b/README.md @@ -30,37 +30,60 @@ The *[JFrog Extension](https://marketplace.visualstudio.com/items?itemName=JFrog ## Table of contents -- [Table of contents](#Table-of-contents) - - [Overview](#Overview) - - [Download and Installation](#Download-and-Installation) - - [Installing the Extension](#Installing-the-Extension) - - [Installing the Build Agent](#Installing-the-Build-Agent) - - [Configuring the Service Connections](#Configuring-the-Service-Connections) - - [Executing JFrog CLI Commands](#Executing-JFrog-CLI-Commands) - - [Build tools Tasks](#build-tools-tasks) - - [JFrog Maven](#JFrog-Maven-Task) - - [JFrog Gradle](#JFrog-Gradle-Task) - - [JFrog Npm](#JFrog-Npm-Task) - - [JFrog Nuget](#JFrog-Nuget-and-NET-Core-Task) - - [JFrog .NET Core](#JFrog-Nuget-and-NET-Core-Task) - - [JFrog Pip](#JFrog-Pip-Task) - - [JFrog Conan](#JFrog-Conan-Task) - - [JFrog Go](#JFrog-Go-Task) - - [Build Tasks](#Build-tasks) - - [JFrog Collect Build Issues](#JFrog-Collect-Build-Issues) - - [JFrog Publish Build Info](#JFrog-Publish-Build-Info) - - [JFrog Build Promotion](#JFrog-Build-Promotion) - - [Discarding Published Builds](#Discarding-Published-Builds-from-Artifactory) - - [Managing Generic Artifacts](#Managing-Generic-Artifacts) - - [JFrog Xray](#JFrog-Xray-tasks) - - [Audit project's dependencies for Security Vulnerabilities](#Audit-projects-dependencies-for-Security-Vulnerabilities) - - [Scanning Published Builds for Security Vulnerabilities with JFrog Xray](#Scanning-Published-Builds-for-Security-Vulnerabilities) - - [JFrog Docker Tasks](#JFrog-Docker-tasks) - - [Pushing and Pulling Docker Images to and from Artifactory](#Pushing-and-Pulling-Docker-Images-to-and-from-Artifactory) - - [Scanning Local Docker Images with JFrog Xray](#Scanning-Local-Docker-Images-with-JFrog-Xray) - - [JFrog Distribution](#Managing-and-Distributing-Release-Bundles) - - [JFrog Distribution Task](#JFrog-Distribution-V2-Task) - - [Contributions](#Contribution) +- [JFrog Azure DevOps Extension](#jfrog-azure-devops-extension) +- [Overview](#overview) + - [Table of contents](#table-of-contents) + - [Download and Installation](#download-and-installation) + - [Installing the Extension](#installing-the-extension) + - [Installing the Build Agent](#installing-the-build-agent) + - [Automatic Installation](#automatic-installation) + - [Custom tools Installation](#custom-tools-installation) + - [Manual Installation](#manual-installation) + - [Installing JFrog CLI](#installing-jfrog-cli) + - [Installing the Maven Extractor](#installing-the-maven-extractor) + - [Installing the Gradle Extractor](#installing-the-gradle-extractor) + - [Installing Conan](#installing-conan) + - [Using TFS 2015](#using-tfs-2015) + - [Configuring the Service Connections](#configuring-the-service-connections) + - [Executing JFrog CLI Commands](#executing-jfrog-cli-commands) + - [JFrog CLI V2 Task](#jfrog-cli-v2-task) + - [Managing Generic Artifacts](#managing-generic-artifacts) + - [Generic artifacts handling](#generic-artifacts-handling) + - [Downloading generic build dependencies from Artifactory](#downloading-generic-build-dependencies-from-artifactory) + - [Uploading generic build artifacts to Artifactory](#uploading-generic-build-artifacts-to-artifactory) + - [Setting / Deleting properties on files in Artifactory](#setting--deleting-properties-on-files-in-artifactory) + - [Moving / Copying / Deleting artifacts in Artifactory](#moving--copying--deleting-artifacts-in-artifactory) + - [Build tools Tasks](#build-tools-tasks) + - [JFrog Maven Task](#jfrog-maven-task) + - [JFrog Gradle Task](#jfrog-gradle-task) + - [JFrog Npm Task](#jfrog-npm-task) + - [JFrog Nuget and .NET Core Task](#jfrog-nuget-and-net-core-task) + - [JFrog Pip Task](#jfrog-pip-task) + - [JFrog Conan Task](#jfrog-conan-task) + - [JFrog Go Task](#jfrog-go-task) + - [Build Tasks](#build-tasks) + - [JFrog Collect Build Issues](#jfrog-collect-build-issues) + - [Configuration properties](#configuration-properties) + - [JFrog Publish Build Info](#jfrog-publish-build-info) + - [JFrog Build Promotion](#jfrog-build-promotion) + - [Using Build Promotion in a Release](#using-build-promotion-in-a-release) + - [Discarding Published Builds from Artifactory](#discarding-published-builds-from-artifactory) + - [JFrog Xray tasks](#jfrog-xray-tasks) + - [Audit project's dependencies for Security Vulnerabilities](#audit-projects-dependencies-for-security-vulnerabilities) + - [Scanning Published Builds for Security Vulnerabilities](#scanning-published-builds-for-security-vulnerabilities) + - [JFrog Docker tasks](#jfrog-docker-tasks) + - [Pushing and Pulling Docker Images to and from Artifactory](#pushing-and-pulling-docker-images-to-and-from-artifactory) + - [Scanning Local Docker Images with JFrog Xray](#scanning-local-docker-images-with-jfrog-xray) + - [Using Published Artifacts in a Release](#using-published-artifacts-in-a-release) + - [Using JFrog Generic Artifacts task](#using-jfrog-generic-artifacts-task) + - [Using Azure Artifact source](#using-azure-artifact-source) + - [Managing and Distributing Release Bundles](#managing-and-distributing-release-bundles) + - [JFrog Distribution V2 Task](#jfrog-distribution-v2-task) + - [Contribution](#contribution) + - [Building](#building) + - [Testing](#testing) + - [Skipping Tests](#skipping-tests) + - [Reporting issues](#reporting-issues) ## Download and Installation @@ -661,6 +684,12 @@ The full documentation of Conan is available at the [conan website](https://docs pathOrReference: './' buildName: '$(Build.DefinitionName)' buildNumber: '$(Build.BuildNumber)' +- task: JFrogConanV2@2 + inputs: + conanCommand: 'Install' + pathOrReference: './' + buildName: '$(Build.DefinitionName)' + buildNumber: '$(Build.BuildNumber)' ``` For more information about Conan repositories, diff --git a/tasks/JFrogConanV2/conanBuild.js b/tasks/JFrogConanV2/conanBuild.js new file mode 100644 index 00000000..87f1a3b6 --- /dev/null +++ b/tasks/JFrogConanV2/conanBuild.js @@ -0,0 +1,246 @@ +const conanutils = require('./conanUtils.js'); +const tl = require('azure-pipelines-task-lib/task'); +const utils = require('@jfrog/tasks-utils/utils.js'); + +function run() { + let conanCommand = tl.getInput('conanCommand', true); + + // Handle different conan commands + switch (conanCommand) { + case 'Config Install': + handleConfigInstallCommand(); + break; + case 'Add Remote': + handleAddRemoteCommand(); + break; + case 'Create': + handleCreateCommand(); + break; + case 'Install': + handleInstallCommand(); + break; + case 'Upload': + handleUploadCommand(); + break; + case 'Custom': + handleCustomCommand(); + break; + default: + tl.setResult(tl.TaskResult.Failed, 'Conan Command not supported: ' + conanCommand); + } +} + +/** + * Handle Conan Config Install Command + */ +function handleConfigInstallCommand() { + let configSourceType = tl.getInput('configSourceType', true); + let configInstallItem = tl.getInput('configInstallItem', true); + let extraArguments = tl.getInput('extraArguments', false); + + let conanArguments = ['config', 'install']; + conanArguments = addExtraArguments(conanArguments, extraArguments); + conanArguments.push('--type'); + conanArguments.push(configSourceType); + conanArguments.push(configInstallItem); + + conanutils + .executeConanTask(conanArguments) + .then(() => { + setTaskResult(true); + }) + .catch((err) => { + console.error('Failed to execute Conan Task: ' + err.message); + setTaskResult(false); + }); +} + +/** + * Handle Conan Add Remote Command + */ +function handleAddRemoteCommand() { + let remoteName = tl.getInput('remoteName', true); + let artifactoryService = tl.getInput('artifactoryConnection', true); + let artifactoryUrl = tl.getEndpointUrl(artifactoryService, false); + let artifactoryUser = tl.getEndpointAuthorizationParameter(artifactoryService, 'username', true); + let artifactoryPassword = tl.getEndpointAuthorizationParameter(artifactoryService, 'password', true); + let artifactoryAccessToken = tl.getEndpointAuthorizationParameter(artifactoryService, 'apitoken', true); + let conanRepo = tl.getInput('conanRepo', true); + let purgeExistingRemotes = tl.getBoolInput('purgeExistingRemotes', true); + + if (artifactoryAccessToken) { + // Access token is not supported. + console.error( + 'Access Token is not supported for authentication with Artifactory, please configure Artifactory service connection' + + ' to work with basic authentication.' + ); + setTaskResult(false); + return; + } + + let conanArguments = [ + 'remote', + 'add', + remoteName, + utils.stripTrailingSlash(artifactoryUrl) + '/api/conan/' + conanRepo, + '--index', + '0', + '--force', + ]; + + // Purge existing + if (purgeExistingRemotes) { + try { + conanutils.purgeConanRemotes(); + } catch (err) { + console.error('Failed to purge Conan Remotes: ' + err.message); + setTaskResult(false); + return; + } + } + + // Add remote repo configuration + conanutils + .executeConanTask(conanArguments) + .then(() => { + // Add user credentials + conanArguments = ['remote', 'login', '--password', artifactoryPassword, remoteName, artifactoryUser]; + conanutils.executeConanTask(conanArguments).then(() => { + setTaskResult(true); + }); + }) + .catch((err) => { + console.error('Failed to execute Conan Task: ' + err.message); + setTaskResult(false); + }); +} + +/** + * Handle Conan Create Command + */ +function handleCreateCommand() { + let createPath = tl.getPathInput('createPath', true, true); + let extraArguments = tl.getInput('extraArguments', false); + + let conanArguments = ['create']; + conanArguments = addExtraArguments(conanArguments, extraArguments); + conanArguments.push(createPath); + + conanutils + .executeConanTask(conanArguments) + .then(() => { + setTaskResult(true); + }) + .catch((err) => { + console.error('Failed to execute Conan Task: ' + err.message); + setTaskResult(false); + }); +} + +/** + * Handle Conan Install Command + */ +function handleInstallCommand() { + let pathOrReference = tl.getInput('pathOrReference', true); + let extraArguments = tl.getInput('extraArguments', false); + + let conanArguments = ['install']; + conanArguments = addExtraArguments(conanArguments, extraArguments); + conanArguments.push(pathOrReference); + + conanutils + .executeConanTask(conanArguments) + .then(() => { + setTaskResult(true); + }) + .catch((err) => { + console.error('Failed to execute Conan Task: ' + err.message); + setTaskResult(false); + }); +} + +/** + * Handle Conan Upload Command + */ +function handleUploadCommand() { + let patternOrReference = tl.getInput('patternOrReference', true); + let extraArguments = tl.getInput('extraArguments', false); + + let conanArguments = ['upload']; + conanArguments = addExtraArguments(conanArguments, extraArguments); + // Enforce --confirm option since the command runs in a non-interactive mode + enforceArgumentOrOption(conanArguments, '-c', '--confirm'); + conanArguments.push(patternOrReference); + + conanutils + .executeConanTask(conanArguments) + .then(() => { + setTaskResult(true); + }) + .catch((err) => { + console.error('Failed to execute Conan Task: ' + err.message); + setTaskResult(false); + }); +} + +/** + * Handle Conan Custom Command + */ +function handleCustomCommand() { + let customArguments = tl.getInput('customArguments', true); + let conanArguments = customArguments.split(' '); + + conanutils + .executeConanTask(conanArguments) + .then(() => { + setTaskResult(true); + }) + .catch((err) => { + console.error('Failed to execute Conan Task: ' + err.message); + setTaskResult(false); + }); +} + +/** + * Add extra arguments to list of options and arguments + * + * @param conanArguments (Array) - Collection of options and arguments + * @param extraArguments (String) - String containing the input of extra options and arguments + */ +function addExtraArguments(conanArguments, extraArguments) { + if (extraArguments && extraArguments.trim().length > 0) { + let extraArgumentsArray = extraArguments.split(' '); + return conanArguments.concat(extraArgumentsArray); + } + return conanArguments; +} + +/** + * Enforce the presence of an option or argument in the list of arguments + * + * @param conanArguments (Array) - Collection of options and arguments + * @param shortVersion (string) - Short version of option or argument + * @param longVersion (string) - Long version of option or argument + */ +function enforceArgumentOrOption(conanArguments, shortVersion, longVersion) { + if (conanArguments.indexOf(shortVersion) < 0 && conanArguments.indexOf(longVersion) < 0) { + conanArguments.push(longVersion); + } +} + +/** + * Set Task Result + * + * @param taskSuccessful (Boolean) - Flag with task result. + * True: Task was Successful. + * False: Task failed. + */ +function setTaskResult(taskSuccessful) { + if (taskSuccessful) { + tl.setResult(tl.TaskResult.Succeeded, 'Conan Task finished.'); + } else { + tl.setResult(tl.TaskResult.Failed, 'Conan Task failed.'); + } +} + +run(); diff --git a/tasks/JFrogConanV2/conanUtils.d.ts b/tasks/JFrogConanV2/conanUtils.d.ts new file mode 100644 index 00000000..5f828208 --- /dev/null +++ b/tasks/JFrogConanV2/conanUtils.d.ts @@ -0,0 +1,6 @@ +// conanUtils.d.ts +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace conan_utils { + function getCliPartialsBuildDir(buildName: string, buildNumber: string): string; +} +export = conan_utils; diff --git a/tasks/JFrogConanV2/conanUtils.js b/tasks/JFrogConanV2/conanUtils.js new file mode 100644 index 00000000..c2f8e043 --- /dev/null +++ b/tasks/JFrogConanV2/conanUtils.js @@ -0,0 +1,409 @@ +const tl = require('azure-pipelines-task-lib/task'); +const uuid = require('uuid/v1'); +const os = require('os'); +const fs = require('fs-extra'); +const path = require('path'); +const crypto = require('crypto'); + +// Helper Constants +const CONAN_ARTIFACTS_PROPERTIES_BUILD_NAME = 'artifact_property_build.name'; +const CONAN_ARTIFACTS_PROPERTIES_BUILD_NUMBER = 'artifact_property_build.number'; +const CONAN_ARTIFACTS_PROPERTIES_BUILD_TIMESTAMP = 'artifact_property_build.timestamp'; +const BUILD_INFO_BUILD_NAME = 'name'; +const BUILD_INFO_BUILD_NUMBER = 'number'; +const BUILD_INFO_BUILD_STARTED = 'started'; +const BUILD_INFO_FILE_NAME = 'generatedBuildInfo'; +const BUILD_TEMP_PATH = 'jfrog/builds'; + +/** + * Execute Artifactory Conan Task + * @param commandArgs (Array) - Conan command arguments + */ +function executeConanTask(commandArgs) { + return new Promise((resolve, reject) => { + let workingDir = tl.getPathInput('workingDirectory', false, false); + let conanUserHome = tl.getInput('conanUserHome', false); + let collectBuildInfo = tl.getBoolInput('collectBuildInfo', false); + + let conanTaskId = generateConanTaskUUId(); + tl.debug('Conan Task Id: ' + conanTaskId); + let buildTimestamp = Date.now(); + + let conanPath = null; + try { + /* + * Get Conan tool Path. This will force the conan task to fail fast if + * conan tool is not available in the PATH + */ + conanPath = tl.which('conan', true); + console.log('Running Conan build tool from: ' + conanPath); + } catch (err) { + reject(new Error('Failed to locate Conan executable path: ' + err)); + } + + /* + * Set Conan Environment Variable + * Conan User Home is set as a variable in the phase scope, so it will be + * available to every task running after this one + */ + if (!conanUserHome) { + conanUserHome = getDefaultConanUserHome(); + } + console.log('Conan User Home: ' + conanUserHome); + tl.setVariable('CONAN_USER_HOME', conanUserHome); + + // Prepare Conan to generate build info + if (collectBuildInfo) { + let buildName = tl.getInput('buildName', true); + let buildNumber = tl.getInput('buildNumber', true); + try { + initCliPartialsBuildDir(buildName, buildNumber); + setConanTraceFileLocation(conanUserHome, conanTaskId); + setArtifactsBuildInfoProperties(conanUserHome, buildName, buildNumber, buildTimestamp); + } catch (err) { + reject(new Error('Failed to setup Build Info collection: ' + err.message)); + } + } + + // Run conan command and set task result + executeConanCommand(conanPath, commandArgs, workingDir) + .then((exitCode) => { + if (exitCode !== 0) { + reject(new Error('Conan command returned bad exit code: ' + exitCode)); + } + }) + .then(() => { + // Generate build info if requested + if (collectBuildInfo) { + generateBuildInfo(conanUserHome, conanTaskId).then((exitCode) => { + if (exitCode !== 0) { + reject(new Error('Failed to generate build info with bad exit code: ' + exitCode)); + } + try { + completeBuildInfo(conanUserHome, conanTaskId); + } catch (err) { + reject(new Error('Failed to make Build Info available: ' + err)); + } + resolve(); + }); + } else { + resolve(); + } + }); + }); +} + +/** + * Generate a unique ID to be used by this Conan task + */ +function generateConanTaskUUId() { + return uuid(); +} + +/** + * Get Default Conan User Home Folder. + * + * If process is a build it will be: + * $(Agent.WorkFolder)/$(System.DefinitionId)/$(Build.BuildId) + * + * If the process is a release it will be: + * $(Agent.WorkFolder)/$(Build.DefinitionId)/$(Build.BuildId) + */ +function getDefaultConanUserHome() { + let hostType = tl.getVariable('System.HostType'); + let workFolder = tl.getVariable('Agent.WorkFolder'); + let buildNumber = tl.getVariable('Build.BuildNumber'); + + // Get Build Id during build process + let buildId = tl.getVariable('System.DefinitionId'); + if (hostType === 'release') { + // Get Build Id during release process + buildId = tl.getVariable('Build.DefinitionId'); + } + + return path.join(workFolder, buildId, buildNumber); +} + +/** + * Create Conan artifacts.properties file with build info information. + * The properties in this file will be attached to all artifacts pushed to + * Artifactory by Conan. + * + * If the file is already present with content related to the specific buildName and + * buildNumber arguments, this file generation is skipped and the current file + * will be used. + * + * If the file is present with content related to a different buildName and buildNumber + * it will be overwritten with the new content. + * + * @param conanUserHome (string) - Conan User Home location + * @param buildName (string) - Build name used to generate buildInfo + * @param buildNumber (string) - Build number used to generate buildInfo + * @param buildTimestamp (string) - Timestamp attached to buildInfo started date + */ +function setArtifactsBuildInfoProperties(conanUserHome, buildName, buildNumber, buildTimestamp) { + let conanArtifactsPropertiesPath = getConanArtifactsPropertiesLocation(conanUserHome); + + // Check if existing content is related to current buildInfo information + if (fs.existsSync(conanArtifactsPropertiesPath)) { + let existingContent = fs.readFileSync(conanArtifactsPropertiesPath); + let propertiesMap = convertConanPropertiesToMap(existingContent.toString()); + if (CONAN_ARTIFACTS_PROPERTIES_BUILD_NAME in propertiesMap && CONAN_ARTIFACTS_PROPERTIES_BUILD_NUMBER in propertiesMap) { + if ( + buildName === propertiesMap[CONAN_ARTIFACTS_PROPERTIES_BUILD_NAME] && + buildNumber === propertiesMap[CONAN_ARTIFACTS_PROPERTIES_BUILD_NUMBER] + ) { + tl.debug('Conan artifacts.properties already set for this build at ' + conanArtifactsPropertiesPath); + return; + } else { + throw new Error('Conan artifacts.properties file exists at ' + conanArtifactsPropertiesPath + ' with different build info values.'); + } + } + } + + // Generate file + let content = + CONAN_ARTIFACTS_PROPERTIES_BUILD_NAME + + '=' + + buildName + + os.EOL + + CONAN_ARTIFACTS_PROPERTIES_BUILD_NUMBER + + '=' + + buildNumber + + os.EOL + + CONAN_ARTIFACTS_PROPERTIES_BUILD_TIMESTAMP + + '=' + + buildTimestamp + + os.EOL; + fs.outputFileSync(conanArtifactsPropertiesPath, content); + tl.debug('Conan artifacts.properties file created at ' + conanArtifactsPropertiesPath); +} + +/** + * Execute Conan Command + * @param conanPath (string) - Path to Conan Tool + * @param commandArgs (Array) - Conan command arguments list + * @param workingDir (string) - Working directory location. If set, command will + * be executed at this location + */ +function executeConanCommand(conanPath, commandArgs, workingDir) { + cleanupConanArguments(commandArgs); + + // Create command + let conan = tl.tool(conanPath).arg(commandArgs); + let options = { + failOnStdErr: false, + errStream: process.stdout, + outStream: process.stdout, + ignoreReturnCode: true, + }; + + // Set working dir if present + if (workingDir) { + // Make sure custom working directory exists + fs.mkdirsSync(workingDir); + } else { + // Use default working dir + workingDir = tl.getVariable('System.DefaultWorkingDirectory'); + } + + console.log('Running Conan command at: ' + workingDir); + options['cwd'] = workingDir; + + // Run command and wait for exitCode + return conan.exec(options); +} + +/** + * Cleanup empty arguments and remove conan keyword from the beginning of arguments + * list if present + * @param commandArgs (Array) - The collection of arguments + */ +function cleanupConanArguments(commandArgs) { + if (commandArgs[0] === 'conan') { + commandArgs.splice(0, 1); + } + for (let i = 0; i < commandArgs.length; i++) { + commandArgs[i] = commandArgs[i].trim(); + if (commandArgs[i] === '') { + commandArgs.splice(i, 1); + // Process the current index again, since one element has been removed + i--; + } + } +} + +/** + * Generate buildInfo Json file. Run conan_build_info tool to convert the conan + * trace file to a buildinfo json file + * @param conanUserHome (string) - Conan User Home location + * @param conanTaskId (string) - Conan Task Id + */ +function generateBuildInfo(conanUserHome, conanTaskId) { + let conanBuildInfoPath = tl.which('conan_build_info', true); + let buildInfoFilePath = getBuildInfoFileLocation(conanUserHome, conanTaskId); + let conanTraceFilePath = process.env.CONAN_TRACE_FILE; + + console.log('Generating Build Info at ' + buildInfoFilePath); + + let conanBuildInfoArgs = [conanTraceFilePath, '--output', buildInfoFilePath]; + + let options = { + failOnStdErr: false, + errStream: process.stdout, + outStream: process.stdout, + ignoreReturnCode: true, + }; + + let conanBuildInfo = tl.tool(conanBuildInfoPath).arg(conanBuildInfoArgs); + + // Run command and wait for exitCode + return conanBuildInfo.exec(options); +} + +/** + * Complete build info json file with information from artifacts.properties + * @param conanUserHome (string) - Conan User Home location + * @param conanTaskId (string) - Conan Task Id + */ +function completeBuildInfo(conanUserHome, conanTaskId) { + let conanArtifactsPropertiesPath = getConanArtifactsPropertiesLocation(conanUserHome); + let buildInfoFilePath = getBuildInfoFileLocation(conanUserHome, conanTaskId); + + // Read artifacts.properties file + let content = fs.readFileSync(conanArtifactsPropertiesPath); + let propertiesMap = convertConanPropertiesToMap(content.toString()); + + let buildName = propertiesMap[CONAN_ARTIFACTS_PROPERTIES_BUILD_NAME]; + let buildNumber = propertiesMap[CONAN_ARTIFACTS_PROPERTIES_BUILD_NUMBER]; + + // Read build info json file + let buildInfoJson = fs.readJsonSync(buildInfoFilePath); + buildInfoJson[BUILD_INFO_BUILD_NAME] = buildName; + buildInfoJson[BUILD_INFO_BUILD_NUMBER] = buildNumber; + let buildTimestamp = new Date(parseInt(propertiesMap[CONAN_ARTIFACTS_PROPERTIES_BUILD_TIMESTAMP], 10)); + buildInfoJson[BUILD_INFO_BUILD_STARTED] = buildTimestamp.toISOString(); + + // Delete the previously created BuildInfo + fs.unlinkSync(buildInfoFilePath); + buildInfoFilePath = path.join(getCliPartialsBuildDir(buildName, buildNumber), BUILD_INFO_FILE_NAME + conanTaskId); + + // Write build info json file + fs.writeJsonSync(buildInfoFilePath, buildInfoJson); + tl.debug('Build Info created at ' + buildInfoFilePath); +} + +/** + * Set enviroment variable with the location for the Conan Trace File + * This file will later be used to generate the buildInfo json file if requested + * by the user + * @param conanUserHome (string) - Conan User Home location + * @param conanTaskId (string) - Conan Task Id + */ +function setConanTraceFileLocation(conanUserHome, conanTaskId) { + process.env.CONAN_TRACE_FILE = path.join(conanUserHome, '.conan', 'conan_trace_' + conanTaskId + '.log'); +} + +/** + * Get build info file location + * @param conanUserHome (string) - Conan User Home location + * @param conanTaskId (string) - Conan Task Id + */ +function getBuildInfoFileLocation(conanUserHome, conanTaskId) { + return path.join(conanUserHome, '.conan', 'build_info_' + conanTaskId + '.json'); +} + +/** + * Get Conan artifacts.properties file location + * @param conanUserHome (string) - Conan User Home location + */ +function getConanArtifactsPropertiesLocation(conanUserHome) { + return path.join(conanUserHome, '.conan', 'artifacts.properties'); +} + +/** + * Convert content in format key=value to a Map + * @param propertiesContent (String) - Properties content + */ +function convertConanPropertiesToMap(propertiesContent) { + if (!propertiesContent) { + return {}; + } + let map = {}; + let lines = propertiesContent.split(os.EOL); + for (let i = 0; i < lines.length; i++) { + let parts = lines[i].split('='); + let key = parts[0]; + parts.splice(0, 1); + map[key] = parts.join('='); + } + return map; +} + +/** + * Purge existing Conan remote repositories + */ +function purgeConanRemotes() { + let conanUserHome = tl.getInput('conanUserHome', false); + + try { + /* + * Get Conan tool Path. This will force the conan task to fail fast if + * conan tool is not available in the PATH + */ + let conanPath = tl.which('conan', true); + console.log('Running Conan build tool from: ' + conanPath); + } catch (err) { + throw new Error('Failed to locate Conan executable path: ' + err.message); + } + + /* + * Set Conan Environment Variable + * Conan User Home is set as a variable in the phase scope, so it will be + * available to every task running after this one + */ + if (!conanUserHome) { + conanUserHome = getDefaultConanUserHome(); + } + console.log('Conan User Home: ' + conanUserHome); + tl.setVariable('CONAN_USER_HOME', conanUserHome); + + // Make sure Conan User Home exists + let conanFolder = path.join(conanUserHome, '.conan'); + fs.mkdirsSync(conanFolder); + + // Empty registry.txt file to remove all existing remotes + let registryFile = path.join(conanFolder, 'registry.txt'); + console.log('Purging Conan remotes by removing content of ' + registryFile); + try { + fs.writeFileSync(registryFile, ''); + } catch (err) { + throw new Error('Failed to remove registry.txt file content: ' + err.message); + } +} + +/** + * Creates the path of for partials build info and initializing the details file with Timestamp. + * @param buildName (string) - The build name + * @param buildNumber (string) - The build number + */ +function initCliPartialsBuildDir(buildName, buildNumber) { + let partialsBuildDir = path.join(getCliPartialsBuildDir(buildName, buildNumber), 'partials'); + if (!fs.pathExistsSync(partialsBuildDir)) { + fs.ensureDirSync(partialsBuildDir); + } + fs.writeJsonSync(path.join(partialsBuildDir, 'details'), { Timestamp: new Date().toISOString() }); + tl.debug('Created partial details at: ' + path.join(partialsBuildDir, 'details')); +} + +function getCliPartialsBuildDir(buildName, buildNumber) { + const buildId = buildName + '_' + buildNumber + '_' + ''; + const hexId = crypto.createHash('sha256').update(buildId).digest('hex'); + return path.join(os.tmpdir(), BUILD_TEMP_PATH, hexId); +} + +module.exports = { + executeConanTask: executeConanTask, + getCliPartialsBuildDir: getCliPartialsBuildDir, // Exported for tests + purgeConanRemotes: purgeConanRemotes, +}; diff --git a/tasks/JFrogConanV2/icon.png b/tasks/JFrogConanV2/icon.png new file mode 100644 index 00000000..189228c5 Binary files /dev/null and b/tasks/JFrogConanV2/icon.png differ diff --git a/tasks/JFrogConanV2/package.json b/tasks/JFrogConanV2/package.json new file mode 100644 index 00000000..977a3156 --- /dev/null +++ b/tasks/JFrogConanV2/package.json @@ -0,0 +1,17 @@ +{ + "name": "@jfrog/jfrog-conan-v2", + "version": "2.6.4", + "author": "JFrog", + "private": true, + "license": "Apache-2.0", + "main": "conanBuild.js", + "typings": "utils", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "@jfrog/tasks-utils": "file:../../jfrog-tasks-utils/jfrog-tasks-utils-1.0.0.tgz", + "fs-extra": "^7.0.1", + "uuid": "^3.3.2" + } +} diff --git a/tasks/JFrogConanV2/task.json b/tasks/JFrogConanV2/task.json new file mode 100644 index 00000000..54d33c8d --- /dev/null +++ b/tasks/JFrogConanV2/task.json @@ -0,0 +1,212 @@ +{ + "id": "3809c250-e06b-4afe-b9d0-334ad742a7d4", + "name": "JFrogConanV2", + "friendlyName": "JFrog Conan V2", + "description": "This task runs a Conan command.", + "author": "JFrog", + "helpMarkDown": "[More Information](https://github.com/jfrog/jfrog-azure-devops-extension#JFrog-conan-Task)", + "category": "Utility", + "visibility": [ + "Build", + "Release" + ], + "demands": [], + "version": { + "Major": "2", + "Minor": "6", + "Patch": "4" + }, + "minimumAgentVersion": "1.83.0", + "instanceNameFormat": "Conan $(conanCommand)", + "groups": [ + { + "name": "advanced", + "displayName": "Advanced", + "isExpanded": false + } + ], + "inputs": [ + { + "name": "conanCommand", + "type": "pickList", + "label": "Conan Command", + "defaultValue": "", + "options": { + "Config Install": "Config Install", + "Add Remote": "Add Remote", + "Install": "Install", + "Create": "Create", + "Upload": "Upload", + "Custom": "Custom" + }, + "required": true, + "helpMarkDown": "The conan command to be run by this task." + }, + { + "name": "remoteName", + "type": "string", + "label": "Conan Remote Name", + "visibleRule": "conanCommand = Add Remote", + "defaultValue": "", + "required": true, + "helpMarkDown": "Alias that can be used to reference this Conan remote repository." + }, + { + "name": "artifactoryConnection", + "type": "connectedService:jfrogArtifactoryService", + "label": "Artifactory service connection", + "visibleRule": "conanCommand = Add Remote", + "defaultValue": "", + "required": true, + "helpMarkDown": "Artifactory service used by this remote configuration." + }, + { + "name": "conanRepo", + "type": "pickList", + "label": "Conan Repository", + "visibleRule": "conanCommand = Add Remote", + "defaultValue": "", + "required": true, + "helpMarkDown": "Conan repository used by this remote configuration.", + "properties": { + "EditableOptions": "True" + } + }, + { + "name": "purgeExistingRemotes", + "type": "boolean", + "label": "Purge Existing Remotes", + "visibleRule": "conanCommand = Add Remote", + "defaultValue": "false", + "required": true, + "helpMarkDown": "Purge existing remotes before adding this new one." + }, + { + "name": "configSourceType", + "type": "radio", + "label": "Source Type", + "visibleRule": "conanCommand = Config Install", + "required": true, + "defaultValue": "file", + "helpMarkDown": "Source type of the configuration file to use.", + "options": { + "dir": "Dir", + "git": "Git", + "file": "File", + "url": "Url" + } + }, + { + "name": "configInstallItem", + "type": "string", + "label": "Configuration Item", + "visibleRule": "conanCommand = Config Install", + "required": true, + "defaultValue": "", + "helpMarkDown": "Configuration Item to be installed." + }, + { + "name": "pathOrReference", + "type": "string", + "label": "Path or Reference", + "visibleRule": "conanCommand = Install", + "required": true, + "defaultValue": "", + "helpMarkDown": "Path to a folder containing a recipe (conanfile.py or conanfile.txt) or to a recipe file. e.g., ./my_project/conanfile.txt. It could also be a reference." + }, + { + "name": "patternOrReference", + "type": "string", + "label": "Pattern or Reference", + "visibleRule": "conanCommand = Upload", + "required": true, + "defaultValue": "", + "helpMarkDown": "Pattern or package recipe reference, e.g., boost/*, MyPackage/1.2@user/channel." + }, + { + "name": "createPath", + "type": "filePath", + "label": "Path", + "visibleRule": "conanCommand = Create", + "required": true, + "defaultValue": "", + "helpMarkDown": "Path to a folder containing a conanfile.py or to a recipe file e.g., my_folder/conanfile.py." + }, + { + "name": "extraArguments", + "type": "string", + "label": "Conan arguments and options", + "visibleRule": "conanCommand = Config Install || conanCommand = Install || conanCommand = Upload || conanCommand = Create", + "required": false, + "defaultValue": "", + "helpMarkDown": "Conan command extra arguments and options." + }, + { + "name": "customArguments", + "type": "string", + "label": "Conan command to execute", + "visibleRule": "conanCommand = Custom", + "required": true, + "defaultValue": "", + "helpMarkDown": "Conan command with options and arguments." + }, + { + "name": "collectBuildInfo", + "type": "boolean", + "label": "Collect build info", + "visibleRule": "conanCommand = Install || conanCommand = Upload || conanCommand = Create || conanCommand = Custom", + "defaultValue": "true", + "required": false, + "helpMarkDown": "Select to collect build info." + }, + { + "name": "buildName", + "type": "string", + "label": "Build name", + "defaultValue": "$(Build.DefinitionName)", + "required": true, + "visibleRule": "collectBuildInfo=true", + "helpMarkDown": "Build name.\nTo use the default build name of the pipeline, set the field to '$(Build.DefinitionName)'.\nThe collected build-info should be published to Artifactory using the 'JFrog Publish Build Info' task." + }, + { + "name": "buildNumber", + "type": "string", + "label": "Build number", + "defaultValue": "$(Build.BuildNumber)", + "required": true, + "visibleRule": "collectBuildInfo=true", + "helpMarkDown": "Build number.\nTo use the default build number of the pipeline, set the field to '$(Build.BuildNumber)'.\nThe collected build-info should be published to Artifactory using the 'JFrog Publish Build Info' task." + }, + { + "name": "conanUserHome", + "type": "string", + "label": "Conan User Home", + "defaultValue": "", + "required": false, + "helpMarkDown": "Conan User Home folder location. The default values for build and release pipelines are $(Agent.WorkFolder)/$(System.DefinitionId)/$(Build.BuildId) and $(Agent.WorkFolder)/$(Build.DefinitionId)/$(Build.BuildId) respectively.", + "groupName": "advanced" + }, + { + "name": "workingDirectory", + "type": "filePath", + "label": "Working Directory", + "defaultValue": "", + "required": false, + "groupName": "advanced" + } + ], + "dataSourceBindings": [ + { + "endpointId": "$(artifactoryConnection)", + "endpointURL": "{{endpoint.url}}/api/repositories", + "target": "conanRepo", + "resultSelector": "jsonpath:$[?(@.type=='VIRTUAL' || @.type=='LOCAL')]", + "resultTemplate": "{ \"Value\" : \"{{{key}}}\", \"DisplayValue\" : \"{{{key}}}\" }" + } + ], + "execution": { + "Node10": { + "target": "conanBuild.js" + } + } +} \ No newline at end of file diff --git a/tests/resources/conanTaskV2/conanAddRemote.js b/tests/resources/conanTaskV2/conanAddRemote.js new file mode 100644 index 00000000..63c5dc06 --- /dev/null +++ b/tests/resources/conanTaskV2/conanAddRemote.js @@ -0,0 +1,26 @@ +const testUtils = require('../../testUtils'); +const path = require('path'); + +const TEST_NAME = path.basename(__dirname); +const BUILD_NAME = TEST_NAME; +const BUILD_NUMBER = '1'; + +let variables = { + 'System.HostType': 'build', + 'System.DefinitionId': BUILD_NAME, + 'Build.BuildDirectory': '/tmp/' + BUILD_NAME, + 'Build.DefinitionName': BUILD_NAME, + 'Build.BuildNumber': BUILD_NUMBER, +}; + +let inputs = { + buildName: BUILD_NAME, + buildNumber: BUILD_NUMBER, + remoteName: 'artifactory', + artifactoryService: '40567017-861d-4e23-8ebf-c71c33a72224', + conanCommand: 'Add Remote', + conanRepo: testUtils.getRepoKeys().conanLocalRepo, + purgeExistingRemotes: false, +}; + +testUtils.runArtifactoryTask(testUtils.conan, variables, inputs); diff --git a/tests/resources/conanTaskV2/conanAddRemoteWithPurge.js b/tests/resources/conanTaskV2/conanAddRemoteWithPurge.js new file mode 100644 index 00000000..c960b97a --- /dev/null +++ b/tests/resources/conanTaskV2/conanAddRemoteWithPurge.js @@ -0,0 +1,27 @@ +const testUtils = require('../../testUtils'); +const path = require('path'); + +const TEST_NAME = path.basename(__dirname); +const BUILD_NAME = TEST_NAME; +const BUILD_NUMBER = '5'; + +let variables = { + 'System.HostType': 'build', + 'System.DefinitionId': BUILD_NAME, + 'Build.BuildDirectory': '/tmp/' + BUILD_NAME, + 'Build.DefinitionName': BUILD_NAME, + 'Build.BuildNumber': BUILD_NUMBER, +}; + +let inputs = { + buildName: BUILD_NAME, + buildNumber: BUILD_NUMBER, + remoteName: 'artifactory', + artifactoryService: '40567017-861d-4e23-8ebf-c71c33a72224', + conanCommand: 'Add Remote', + conanRepo: testUtils.getRepoKeys().conanLocalRepo, + purgeExistingRemotes: true, + conanUserHome: '/tmp/custom/' + BUILD_NAME + '/' + BUILD_NUMBER, +}; + +testUtils.runArtifactoryTask(testUtils.conan, variables, inputs); diff --git a/tests/resources/conanTaskV2/conanConfigInstall.js b/tests/resources/conanTaskV2/conanConfigInstall.js new file mode 100644 index 00000000..7c79403f --- /dev/null +++ b/tests/resources/conanTaskV2/conanConfigInstall.js @@ -0,0 +1,25 @@ +const testUtils = require('../../testUtils'); +const path = require('path'); + +const TEST_NAME = path.basename(__dirname); +const BUILD_NAME = TEST_NAME; +const BUILD_NUMBER = '3'; + +let variables = { + 'System.HostType': 'build', + 'System.DefinitionId': BUILD_NAME, + 'Build.BuildDirectory': '/tmp/' + BUILD_NAME, + 'Build.DefinitionName': BUILD_NAME, + 'Build.BuildNumber': BUILD_NUMBER, +}; + +let inputs = { + buildName: BUILD_NAME, + buildNumber: BUILD_NUMBER, + conanCommand: 'Config Install', + configSourceType: 'url', + configInstallItem: 'file://conan-config.zip', + extraArguments: '', +}; + +testUtils.runArtifactoryTask(testUtils.conan, variables, inputs); diff --git a/tests/resources/conanTaskV2/conanCreate.js b/tests/resources/conanTaskV2/conanCreate.js new file mode 100644 index 00000000..17d1a531 --- /dev/null +++ b/tests/resources/conanTaskV2/conanCreate.js @@ -0,0 +1,25 @@ +const testUtils = require('../../testUtils'); +const path = require('path'); + +const TEST_NAME = path.basename(__dirname); +const BUILD_NAME = TEST_NAME; +const BUILD_NUMBER = '1'; + +let variables = { + 'System.HostType': 'build', + 'System.DefinitionId': BUILD_NAME, + 'Build.BuildDirectory': '/tmp/' + BUILD_NAME, + 'Build.DefinitionName': BUILD_NAME, + 'Build.BuildNumber': BUILD_NUMBER, +}; + +let inputs = { + buildName: BUILD_NAME, + buildNumber: BUILD_NUMBER, + conanCommand: 'Create', + createPath: path.join(__dirname, 'files', 'conan-min'), + extraArguments: '--build missing', + collectBuildInfo: true, +}; + +testUtils.runArtifactoryTask(testUtils.conan, variables, inputs); diff --git a/tests/resources/conanTaskV2/conanCustomCommand.js b/tests/resources/conanTaskV2/conanCustomCommand.js new file mode 100644 index 00000000..8d08e8ae --- /dev/null +++ b/tests/resources/conanTaskV2/conanCustomCommand.js @@ -0,0 +1,24 @@ +const testUtils = require('../../testUtils'); +const path = require('path'); + +const TEST_NAME = path.basename(__dirname); +const BUILD_NAME = TEST_NAME; +const BUILD_NUMBER = '1'; + +let variables = { + 'System.HostType': 'build', + 'System.DefinitionId': BUILD_NAME, + 'Build.BuildDirectory': '/tmp/' + BUILD_NAME, + 'Build.DefinitionName': BUILD_NAME, + 'Build.BuildNumber': BUILD_NUMBER, +}; + +let inputs = { + buildName: BUILD_NAME, + buildNumber: BUILD_NUMBER, + conanCommand: 'Custom', + customArguments: 'remote list', + conanUserHome: '/tmp/custom/' + BUILD_NAME + '/' + BUILD_NUMBER, +}; + +testUtils.runArtifactoryTask(testUtils.conan, variables, inputs); diff --git a/tests/resources/conanTaskV2/conanCustomCommandWithBuildInfo.js b/tests/resources/conanTaskV2/conanCustomCommandWithBuildInfo.js new file mode 100644 index 00000000..9db154ff --- /dev/null +++ b/tests/resources/conanTaskV2/conanCustomCommandWithBuildInfo.js @@ -0,0 +1,24 @@ +const testUtils = require('../../testUtils'); +const path = require('path'); + +const TEST_NAME = path.basename(__dirname); +const BUILD_NAME = TEST_NAME; +const BUILD_NUMBER = '2'; + +let variables = { + 'System.HostType': 'build', + 'System.DefinitionId': BUILD_NAME, + 'Build.BuildDirectory': '/tmp/' + BUILD_NAME, + 'Build.DefinitionName': BUILD_NAME, + 'Build.BuildNumber': BUILD_NUMBER, +}; + +let inputs = { + buildName: BUILD_NAME, + buildNumber: BUILD_NUMBER, + conanCommand: 'Custom', + customArguments: 'remote list', + collectBuildInfo: true, +}; + +testUtils.runArtifactoryTask(testUtils.conan, variables, inputs); diff --git a/tests/resources/conanTaskV2/conanCustomCommandWithWorkingDir.js b/tests/resources/conanTaskV2/conanCustomCommandWithWorkingDir.js new file mode 100644 index 00000000..5543dfa4 --- /dev/null +++ b/tests/resources/conanTaskV2/conanCustomCommandWithWorkingDir.js @@ -0,0 +1,24 @@ +const testUtils = require('../../testUtils'); +const path = require('path'); + +const TEST_NAME = path.basename(__dirname); +const BUILD_NAME = TEST_NAME; +const BUILD_NUMBER = '1'; + +let variables = { + 'System.HostType': 'build', + 'System.DefinitionId': BUILD_NAME, + 'Build.BuildDirectory': '/tmp/' + BUILD_NAME, + 'Build.DefinitionName': BUILD_NAME, + 'Build.BuildNumber': BUILD_NUMBER, +}; + +let inputs = { + buildName: BUILD_NAME, + buildNumber: BUILD_NUMBER, + conanCommand: 'Custom', + customArguments: 'conan remote list', + workingDirectory: '/tmp', +}; + +testUtils.runArtifactoryTask(testUtils.conan, variables, inputs); diff --git a/tests/resources/conanTaskV2/conanCustomInvalidCommand.js b/tests/resources/conanTaskV2/conanCustomInvalidCommand.js new file mode 100644 index 00000000..aa503837 --- /dev/null +++ b/tests/resources/conanTaskV2/conanCustomInvalidCommand.js @@ -0,0 +1,22 @@ +const testUtils = require('../../testUtils'); +const path = require('path'); + +const TEST_NAME = path.basename(__dirname); +const BUILD_NAME = TEST_NAME; +const BUILD_NUMBER = '1'; + +let variables = { + 'System.HostType': 'build', + 'System.DefinitionId': BUILD_NAME, + 'Build.BuildDirectory': '/tmp/' + BUILD_NAME, + 'Build.DefinitionName': BUILD_NAME, + 'Build.BuildNumber': BUILD_NUMBER, +}; +let inputs = { + buildName: BUILD_NAME, + buildNumber: BUILD_NUMBER, + conanCommand: 'Custom', + customArguments: 'invalid', +}; + +testUtils.runArtifactoryTask(testUtils.conan, variables, inputs); diff --git a/tests/resources/conanTaskV2/conanInstall.js b/tests/resources/conanTaskV2/conanInstall.js new file mode 100644 index 00000000..ea2dcccf --- /dev/null +++ b/tests/resources/conanTaskV2/conanInstall.js @@ -0,0 +1,26 @@ +const testUtils = require('../../testUtils'); +const path = require('path'); + +const TEST_NAME = path.basename(__dirname); +const BUILD_NAME = TEST_NAME; +const BUILD_NUMBER = '2'; + +let variables = { + 'System.HostType': 'build', + 'System.DefinitionId': BUILD_NAME, + 'Build.BuildDirectory': '/tmp/' + BUILD_NAME, + 'Build.DefinitionName': BUILD_NAME, + 'Build.BuildNumber': BUILD_NUMBER, +}; + +let inputs = { + buildName: BUILD_NAME, + buildNumber: BUILD_NUMBER, + conanCommand: 'Install', + pathOrReference: '..', + workingDirectory: path.join(__dirname, 'files', 'conan-install', 'build'), + extraArguments: '--build missing', + collectBuildInfo: true, +}; + +testUtils.runArtifactoryTask(testUtils.conan, variables, inputs); diff --git a/tests/resources/conanTaskV2/conanUpload.js b/tests/resources/conanTaskV2/conanUpload.js new file mode 100644 index 00000000..047c1dee --- /dev/null +++ b/tests/resources/conanTaskV2/conanUpload.js @@ -0,0 +1,25 @@ +const testUtils = require('../../testUtils'); +const path = require('path'); + +const TEST_NAME = path.basename(__dirname); +const BUILD_NAME = TEST_NAME; +const BUILD_NUMBER = '1'; + +let variables = { + 'System.HostType': 'build', + 'System.DefinitionId': BUILD_NAME, + 'Build.BuildDirectory': '/tmp/' + BUILD_NAME, + 'Build.DefinitionName': BUILD_NAME, + 'Build.BuildNumber': BUILD_NUMBER, +}; + +let inputs = { + buildName: BUILD_NAME, + buildNumber: BUILD_NUMBER, + conanCommand: 'Upload', + patternOrReference: 'Conan-min*', + extraArguments: '-r artifactory --all', + collectBuildInfo: true, +}; + +testUtils.runArtifactoryTask(testUtils.conan, variables, inputs); diff --git a/tests/resources/conanTaskV2/conanUploadInRelease.js b/tests/resources/conanTaskV2/conanUploadInRelease.js new file mode 100644 index 00000000..95abda51 --- /dev/null +++ b/tests/resources/conanTaskV2/conanUploadInRelease.js @@ -0,0 +1,24 @@ +const testUtils = require('../../testUtils'); +const path = require('path'); + +const TEST_NAME = path.basename(__dirname); +const BUILD_NAME = TEST_NAME; +const BUILD_NUMBER = '1'; + +let variables = { + 'System.HostType': 'release', + 'Build.DefinitionId': BUILD_NAME, + 'Build.DefinitionName': BUILD_NAME, + 'Build.BuildNumber': BUILD_NUMBER, +}; + +let inputs = { + buildName: BUILD_NAME, + buildNumber: BUILD_NUMBER, + conanCommand: 'Upload', + patternOrReference: 'Conan-min*', + extraArguments: '-r artifactory --all', + collectBuildInfo: true, +}; + +testUtils.runArtifactoryTask(testUtils.conan, variables, inputs); diff --git a/tests/resources/conanTaskV2/files/conan-config/conan-config.zip b/tests/resources/conanTaskV2/files/conan-config/conan-config.zip new file mode 100644 index 00000000..ce7833ce Binary files /dev/null and b/tests/resources/conanTaskV2/files/conan-config/conan-config.zip differ diff --git a/tests/resources/conanTaskV2/files/conan-install/.gitignore b/tests/resources/conanTaskV2/files/conan-install/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/tests/resources/conanTaskV2/files/conan-install/.gitignore @@ -0,0 +1 @@ +build diff --git a/tests/resources/conanTaskV2/files/conan-install/CMakeLists.txt b/tests/resources/conanTaskV2/files/conan-install/CMakeLists.txt new file mode 100644 index 00000000..4d2bbafa --- /dev/null +++ b/tests/resources/conanTaskV2/files/conan-install/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_CXX_STANDARD 17) + +project(testproj) + +# If conan is being used, configure CMake to use conan for dependencies. +include(${CMAKE_BINARY_DIR}/conan_paths.cmake OPTIONAL) + +add_subdirectory(src) \ No newline at end of file diff --git a/tests/resources/conanTaskV2/files/conan-install/conanfile.txt b/tests/resources/conanTaskV2/files/conan-install/conanfile.txt new file mode 100644 index 00000000..1c7e5871 --- /dev/null +++ b/tests/resources/conanTaskV2/files/conan-install/conanfile.txt @@ -0,0 +1,5 @@ +[requires] +fmt/[>=8.0.1] + +[generators] +cmake_paths \ No newline at end of file diff --git a/tests/resources/conanTaskV2/files/conan-install/src/main.cpp b/tests/resources/conanTaskV2/files/conan-install/src/main.cpp new file mode 100644 index 00000000..777a54a8 --- /dev/null +++ b/tests/resources/conanTaskV2/files/conan-install/src/main.cpp @@ -0,0 +1,10 @@ +#include + +#include + +int main() { + std::string msg = fmt::format("{} + {} = {}", 1, 2, 3); + + std::cout << "Hello, World: " << msg << std::endl; + return 0; +} \ No newline at end of file diff --git a/tests/resources/conanTaskV2/files/conan-min/conanfile.py b/tests/resources/conanTaskV2/files/conan-min/conanfile.py new file mode 100644 index 00000000..eefb8f4e --- /dev/null +++ b/tests/resources/conanTaskV2/files/conan-min/conanfile.py @@ -0,0 +1,38 @@ +from conans import ConanFile, CMake, tools + + +class ConanminConan(ConanFile): + name = "Conan-min" + version = "0.0.1" + license = "" + url = "" + description = "" + settings = "os", "compiler", "build_type", "arch" + options = {"shared": [True, False]} + default_options = "shared=False" + generators = "cmake" + exports_sources = "src/*" + + def requirements(self): + self.requires("boost/[>=1.77]") + + def build(self): + cmake = CMake(self) + cmake.configure(source_folder="src") + cmake.build() + + # Explicit way: + # self.run('cmake %s/hello %s' + # % (self.source_folder, cmake.command_line)) + # self.run("cmake --build . %s" % cmake.build_config) + + def package(self): + self.copy("*.h", dst="include", src="hello") + self.copy("*hello.lib", dst="lib", keep_path=False) + self.copy("*.dll", dst="bin", keep_path=False) + self.copy("*.so", dst="lib", keep_path=False) + self.copy("*.dylib", dst="lib", keep_path=False) + self.copy("*.a", dst="lib", keep_path=False) + + def package_info(self): + self.cpp_info.libs = ["hello"] diff --git a/tests/resources/conanTaskV2/files/conan-min/src/CMakeLists.txt b/tests/resources/conanTaskV2/files/conan-min/src/CMakeLists.txt new file mode 100644 index 00000000..acf5c2be --- /dev/null +++ b/tests/resources/conanTaskV2/files/conan-min/src/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.5) + +project(MyHello CXX) + +include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) + +add_library(hello hello.cpp) diff --git a/tests/resources/conanTaskV2/files/conan-min/src/hello.cpp b/tests/resources/conanTaskV2/files/conan-min/src/hello.cpp new file mode 100644 index 00000000..235428a0 --- /dev/null +++ b/tests/resources/conanTaskV2/files/conan-min/src/hello.cpp @@ -0,0 +1,10 @@ +#include +#include "hello.h" + +void hello(){ + #ifdef NDEBUG + std::cout << "Hello World Release!" < { ); }); + describe('Conan V2 Task Tests', (): void => { + runSyncTest( + 'Conan Custom Command', + (): void => { + const testDir: string = 'conanTaskV2'; + mockTask(testDir, 'conanCustomCommand'); + }, + TestUtils.isSkipTest('conan') + ); + + runSyncTest( + 'Conan Custom Command With Working Dir', + (): void => { + const testDir: string = 'conanTaskV2'; + mockTask(testDir, 'conanCustomCommandWithWorkingDir'); + }, + TestUtils.isSkipTest('conan') + ); + + runSyncTest( + 'Conan Custom Invalid Command', + (): void => { + const testDir: string = 'conanTaskV2'; + mockTask(testDir, 'conanCustomInvalidCommand', true); + }, + TestUtils.isSkipTest('conan') + ); + + runSyncTest( + 'Conan Custom Command With Build Info', + (): void => { + const testDir: string = 'conanTaskV2'; + mockTask(testDir, 'conanCustomCommandWithBuildInfo'); + }, + TestUtils.isSkipTest('conan') + ); + + runSyncTest( + 'Conan Add Remote', + (): void => { + const testDir: string = 'conanTaskV2'; + mockTask(testDir, 'conanAddRemote'); + }, + TestUtils.isSkipTest('conan') + ); + + runSyncTest( + 'Conan Add Remote With Purge', + (): void => { + const testDir: string = 'conanTaskV2'; + mockTask(testDir, 'conanAddRemoteWithPurge'); + }, + TestUtils.isSkipTest('conan') + ); + + runSyncTest( + 'Conan Create And Upload', + (): void => { + const testDir: string = 'conanTaskV2'; + mockTask(testDir, 'conanAddRemote'); + mockTask(testDir, 'conanCreate'); + mockTask(testDir, 'conanUpload'); + }, + TestUtils.isSkipTest('conan') + ); + + runSyncTest( + 'Conan Create And Upload in Release', + (): void => { + const testDir: string = 'conanTaskV2'; + mockTask(testDir, 'conanAddRemote'); + mockTask(testDir, 'conanCreate'); + mockTask(testDir, 'conanUploadInRelease'); + }, + TestUtils.isSkipTest('conan') + ); + + runSyncTest( + 'Conan Install', + (): void => { + const testDir: string = 'conanTaskV2'; + mockTask(testDir, 'conanInstall'); + }, + TestUtils.isSkipTest('conan') + ); + + runSyncTest( + 'Conan Add Config', + (): void => { + const testDir: string = 'conanTaskV2'; + mockTask(testDir, 'conanConfigInstall'); + }, + TestUtils.isSkipTest('conan') + ); + + runSyncTest( + 'Conan Publish Build Info', + (): void => { + const testDir: string = 'conanTaskV2'; + mockTask(testDir, 'conanAddRemote'); + mockTask(testDir, 'conanCreate'); + mockTask(testDir, 'conanUpload'); + mockTask(testDir, 'publishBuildInfo'); + getAndAssertBuild('conanTaskV2', '1'); + deleteBuild('conanTaskV2'); + }, + TestUtils.isSkipTest('conan') + ); + }); + describe('Pip Tests', (): void => { runSyncTest( 'Pip Install', @@ -1211,6 +1322,9 @@ function testGetCliPartialsBuildDir(): void { testDTO.testsBuildNames.forEach((element: string): void => runBuildCommand('bce', element, testDTO.testBuildNumber)); // Assert the partials created at the generated by getCliPartialsBuildDir() path testDTO.testsBuildNames.forEach((element: string): void => assertPathExists(conanUtils.getCliPartialsBuildDir(element, testDTO.testBuildNumber))); + testDTO.testsBuildNames.forEach((element: string): void => + assertPathExists(conanUtilsV2.getCliPartialsBuildDir(element, testDTO.testBuildNumber)) + ); // Cleanup the created partials testDTO.testsBuildNames.forEach((element: string): void => runBuildCommand('bc', element, testDTO.testBuildNumber)); } diff --git a/vss-extension.json b/vss-extension.json index 6d96ef09..ea8b1906 100644 --- a/vss-extension.json +++ b/vss-extension.json @@ -722,6 +722,16 @@ "name": "tasks/JFrogConan" } }, + { + "id": "jfrog-conan-build-task", + "type": "ms.vss-distributed-task.task", + "targets": [ + "ms.vss-distributed-task.tasks" + ], + "properties": { + "name": "tasks/JFrogConanV2" + } + }, { "id": "jfrog-build-scan-task", "type": "ms.vss-distributed-task.task", @@ -871,6 +881,9 @@ { "path": "tasks/JFrogConan" }, + { + "path": "tasks/JFrogConanV2" + }, { "path": "tasks/JFrogBuildScan" },