Skip to content

Commit

Permalink
Merge pull request #1019 from lamarrr/conda-support
Browse files Browse the repository at this point in the history
Add Conda Support
  • Loading branch information
lumaxis authored Mar 19, 2024
2 parents aa09bd7 + 77578e3 commit ca82f80
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 2 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ The format of harvested data is tool-specific. Tool output is stored in the tool
- composer
- rubygem
- deb
- conda

## Provider Registry

Expand All @@ -258,6 +259,9 @@ The format of harvested data is tool-specific. Tool output is stored in the tool
- packagist.org
- proxy.golang.org
- ftp.debian.org
- repo.anaconda.com/pkgs/main (anaconda-main)
- repo.anaconda.com/pkgs/r (anaconda-r)
- conda.anaconda.org/conda-forge (conda-forge)

## Tool Name Registry

Expand Down
1 change: 1 addition & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ function createApp(config) {
app.use('/', require('./routes/index')(config.buildsha))
app.use('/origins/github', require('./routes/originGitHub')())
app.use('/origins/crate', require('./routes/originCrate')())
app.use('/origins/conda', require('./routes/originConda')())
app.use('/origins/pod', require('./routes/originPod')())
app.use('/origins/npm', require('./routes/originNpm')())
app.use('/origins/maven', require('./routes/originMaven')())
Expand Down
2 changes: 2 additions & 0 deletions business/statsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class StatsService {
_getStatLookup() {
return {
total: () => this._getType('total'),
conda: () => this._getType('conda'),
condasrc: () => this._getType('condasrc'),
crate: () => this._getType('crate'),
gem: () => this._getType('gem'),
git: () => this._getType('git'),
Expand Down
5 changes: 5 additions & 0 deletions docs/determining-declared-license.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,8 @@
* source: https://cocoapods.org/
* The service then sets the declared license based on the registry information
* The ClearlyDefined summarizer pulls registry information from 'https://raw.githubusercontent.com/CocoaPods/Specs/master

### conda
* source: conda-forge anaconda-main, or anaconda-r (https://conda.anaconda.org)
* The crawler gets registry information from https://conda.anaconda.org/conda-forge
* The ClearlyDefined summarizer sets the declared license to the license(s) in the registry information
4 changes: 4 additions & 0 deletions lib/licenseMatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ class HarvestLicenseMatchPolicy {
switch (type) {
case 'maven':
return new BaseHarvestLicenseMatchStrategy('maven', ['manifest.summary.licenses'])
case 'conda':
return new BaseHarvestLicenseMatchStrategy('conda', ['declaredLicenses'])
case 'condasrc':
return new BaseHarvestLicenseMatchStrategy('condasrc', ['declaredLicenses'])
case 'crate':
return new BaseHarvestLicenseMatchStrategy('crate', ['registryData.license'])
case 'pod':
Expand Down
29 changes: 29 additions & 0 deletions providers/summary/clearlydefined.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ const mavenBasedUrls = {
gradleplugin: 'https://plugins.gradle.org/m2'
}

const condaChannels = {
'anaconda-main': 'https://repo.anaconda.com/pkgs/main',
'anaconda-r': 'https://repo.anaconda.com/pkgs/r',
'conda-forge': 'https://conda.anaconda.org/conda-forge'
}

class ClearlyDescribedSummarizer {
constructor(options) {
this.options = options
Expand All @@ -44,6 +50,12 @@ class ClearlyDescribedSummarizer {
case 'npm':
this.addNpmData(result, data, coordinates)
break
case 'conda':
this.addCondaData(result, data, coordinates)
break
case 'condasrc':
this.addCondaSrcData(result, data, coordinates)
break
case 'crate':
this.addCrateData(result, data, coordinates)
break
Expand Down Expand Up @@ -191,6 +203,23 @@ class ClearlyDescribedSummarizer {
if (licenses.length) setIfValue(result, 'licensed.declared', SPDX.normalize(licenses.join(' OR ')))
}

addCondaData(result, data, coordinates) {
setIfValue(result, 'described.releaseDate', extractDate(get(data, 'releaseDate')))
setIfValue(result, 'described.urls.download', get(data, 'registryData.downloadUrl'))
setIfValue(result, 'described.urls.registry', new URL(`${condaChannels[coordinates.provider]}`).href)
setIfValue(result, 'described.projectWebsite', get(data, 'registryData.channelData.home'))
setIfValue(result, 'licensed.declared', SPDX.normalize(data.declaredLicenses))
}

addCondaSrcData(result, data, coordinates) {
setIfValue(result, 'described.releaseDate', extractDate(data.releaseDate))
setIfValue(result, 'described.urls.download', get(data, 'registryData.channelData.source_url'))
setIfValue(result, 'described.urls.registry', new URL(`${condaChannels[coordinates.provider]}`).href)
setIfValue(result, 'described.projectWebsite', get(data, 'registryData.channelData.home'))
setIfValue(result, 'licensed.declared', SPDX.normalize(data.declaredLicenses))
}


addCrateData(result, data, coordinates) {
setIfValue(result, 'described.releaseDate', extractDate(get(data, 'registryData.created_at')))
setIfValue(result, 'described.projectWebsite', get(data, 'manifest.homepage'))
Expand Down
101 changes: 101 additions & 0 deletions routes/originConda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation and others. Licensed under the MIT license.
// SPDX-License-Identifier: MIT

const asyncMiddleware = require('../middleware/asyncMiddleware')
const router = require('express').Router()
const requestPromise = require('request-promise-native')
const { uniq } = require('lodash')
const { Cache } = require('memory-cache')
const condaChannels = {
'anaconda-main': 'https://repo.anaconda.com/pkgs/main',
'anaconda-r': 'https://repo.anaconda.com/pkgs/r',
'conda-forge': 'https://conda.anaconda.org/conda-forge'
}
const condaCache = new Cache()

async function fetchCondaChannelData(channel) {
const key = `${channel}-channelData`
let channelData = condaCache.get(key)
if (!channelData) {
const url = `${condaChannels[channel]}/channeldata.json`
channelData = await requestPromise({ url, method: 'GET', json: true })
condaCache.put(key, channelData, 8 * 60 * 60 * 1000) // 8 hours
}
return channelData
}

async function fetchCondaRepoData(channel, subdir) {
const key = `${channel}-${subdir}-repoData`
let repoData = condaCache.get(key)
if (!repoData) {
const url = `${condaChannels[channel]}/${subdir}/repodata.json`
repoData = await requestPromise({ url, method: 'GET', json: true })
condaCache.put(key, repoData, 8 * 60 * 60 * 1000) // 8 hours
}
return repoData
}

router.get(
'/:channel/:subdir/:name/revisions',
asyncMiddleware(async (request, response) => {
let { channel, subdir, name } = request.params
channel = encodeURIComponent(channel)
subdir = encodeURIComponent(subdir)
name = encodeURIComponent(channel)
if (!condaChannels[channel]) {
return response.status(404).send(`Unrecognized Conda channel ${channel}`)
}
let channelData = await fetchCondaChannelData(channel)
if (!channelData.packages[name]) {
return response.status(404).send(`Package ${name} not found in Conda channel ${channel}`)
}
if (subdir !== '-' && !channelData.subdirs.find(x => x == subdir)) {
return response.status(404).send(`Subdir ${subdir} is non-existent in Conda channel ${channel}`)
}
let revisions = []
let subdirs = subdir === '-' ? channelData.packages[name].subdirs : [subdir]
for (let subdir of subdirs) {
const repoData = await fetchCondaRepoData(channel, subdir)
if (repoData['packages']) {
Object.entries(repoData['packages'])
.forEach(([, packageData]) => {
if (packageData.name === name) {
revisions.push(`${subdir}:${packageData.version}-${packageData.build}`)
}
})
}
if (repoData['packages.conda']) {
Object.entries(repoData['packages.conda'])
.forEach(([, packageData]) => {
if (packageData.name === name) {
revisions.push(`${subdir}:${packageData.version}-${packageData.build}`)
}
})
}
}
return response.status(200).send(uniq(revisions))
})
)

router.get(
'/:channel/:name',
asyncMiddleware(async (request, response) => {
let { channel, name } = request.params
channel = encodeURIComponent(channel)
name = encodeURIComponent(name)
if (!condaChannels[channel]) {
return response.status(404).send([])
}
let channelData = await fetchCondaChannelData(channel)
let matches = Object.entries(channelData.packages).filter(([packageName,]) => packageName.includes(name)).map(
([packageName,]) => { return { id: packageName } }
)
return response.status(200).send(matches)
})
)

function setup() {
return router
}

module.exports = setup
5 changes: 5 additions & 0 deletions schemas/coordinates-1.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"type": {
"enum": [
"npm",
"conda",
"condasrc",
"crate",
"git",
"maven",
Expand All @@ -32,8 +34,11 @@
},
"provider": {
"enum": [
"anaconda-main",
"anaconda-r",
"npmjs",
"cocoapods",
"conda-forge",
"cratesio",
"github",
"gitlab",
Expand Down
5 changes: 5 additions & 0 deletions schemas/curation-1.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"type": "string",
"enum": [
"npm",
"conda",
"condasrc",
"crate",
"git",
"maven",
Expand All @@ -45,8 +47,11 @@
"provider": {
"type": "string",
"enum": [
"anaconda-main",
"anaconda-r",
"npmjs",
"cocoapods",
"conda-forge",
"cratesio",
"github",
"gitlab",
Expand Down
5 changes: 5 additions & 0 deletions schemas/curations-1.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"type": "string",
"enum": [
"npm",
"conda",
"condasrc",
"crate",
"git",
"go",
Expand All @@ -47,8 +49,11 @@
"provider": {
"type": "string",
"enum": [
"anaconda-main",
"anaconda-r",
"npmjs",
"cocoapods",
"conda-forge",
"cratesio",
"github",
"gitlab",
Expand Down
7 changes: 6 additions & 1 deletion schemas/definition-1.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"type": {
"enum": [
"npm",
"conda",
"condasrc",
"crate",
"git",
"maven",
Expand All @@ -48,8 +50,11 @@
},
"provider": {
"enum": [
"anaconda-main",
"anaconda-r",
"npmjs",
"cocoapods",
"conda-forge",
"cratesio",
"github",
"gitlab",
Expand Down Expand Up @@ -471,4 +476,4 @@
}
}
}
}
}
8 changes: 7 additions & 1 deletion schemas/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ components:
example:
- git/github/microsoft/redie/194269b5b7010ad6f8dc4ef608c88128615031ca
- npm/npmjs/-/redie/0.3.0
- conda/conda-forge/linux-64/21cmfast/3.1.1-py36

noticeFile:
type: object
Expand Down Expand Up @@ -531,6 +532,8 @@ components:
type: string
enum:
- composer
- conda
- condasrc
- crate
- deb
- debsrc
Expand All @@ -551,7 +554,10 @@ components:
schema:
type: string
enum:
- anaconda-main
- anaconda-r
- cocoapods
- conda-forge
- cratesio
- debian
- github
Expand All @@ -568,7 +574,7 @@ components:
name: namespace
in: path
required: true
description: many component systems have namespaces. GitHub orgs, NPM namespace, Maven group id, ... This segment must be supplied. If your component does not have a namespace, use '-' (ASCII hyphen).
description: many component systems have namespaces. GitHub orgs, NPM namespace, Maven group id, Conda Subdir/Architecture ... This segment must be supplied. If your component does not have a namespace, use '-' (ASCII hyphen).
schema:
type: string
name:
Expand Down
43 changes: 43 additions & 0 deletions test/summary/clearlydefinedTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,49 @@ describe('ClearlyDescribedSummarizer add files', () => {
})
})

describe('ClearlyDescribedSummarizer addCondaData', () => {
const condaTestCoordinates = EntityCoordinates.fromString('conda/conda-forge/-/test/1.0')
it('declares license from registryData', () => {
let result = {}
summarizer.addCondaData(result, { declaredLicenses: 'MIT' }, condaTestCoordinates)
assert.strictEqual(get(result, 'licensed.declared'), 'MIT')
})

it('declares dual license from registryData with SPDX expression', () => {
let result = {}
let data = setup([{ path: 'LICENSE-MIT', license: 'MIT' }, { path: 'LICENSE-APACHE', license: 'Apache-2.0' }])
data.declaredLicenses = 'MIT OR Apache-2.0'
summarizer.addCondaData(result, data, condaTestCoordinates)
assert.strictEqual(get(result, 'licensed.declared'), 'MIT OR Apache-2.0')
})

it('normalizes to spdx only', () => {
let result = {}
summarizer.addCondaData(result, { declaredLicenses: 'Garbage' }, condaTestCoordinates)
assert.strictEqual(get(result, 'licensed.declared'), 'NOASSERTION')
})

it('describes projectWebsite from registryData', () => {
let result = {}
summarizer.addCondaData(result, {
registryData: {
channelData: { home: 'https://github.com/owner/repo' }
}
}, condaTestCoordinates)
assert.strictEqual(result.described.projectWebsite, 'https://github.com/owner/repo')
})

it('describes releaseDate from registryData', () => {
let result = {}
summarizer.addCondaData(
result,
{ releaseDate: 'Wed, 14 Jun 2017 07:00:00 GMT' },
condaTestCoordinates
)
assert.strictEqual(result.described.releaseDate, '2017-06-14')
})
})

describe('ClearlyDescribedSummarizer addCrateData', () => {
const crateTestCoordinates = EntityCoordinates.fromString('crate/cratesio/-/test/1.0')
it('declares license from registryData', () => {
Expand Down

0 comments on commit ca82f80

Please sign in to comment.